المستوي: محترف — المقال ده بيفترض إن عندك خبرة عملية بـ PostgreSQL، كتبت SELECT و JOIN على جداول أكبر من مليون صف، وعندك إلمام بأساسيات B-Tree Index. لو لسه في البداية، ابدأ بمقال B-Tree Indexes للمبتدئ في نفس القسم وارجع هنا.
لو ضفت Index على جدول 12 مليون صف وفاجأك إن نفس الـ query لسه بياخد 2.87 ثانية، PostgreSQL مش بيهربلك. هو قرّر إن الـ Index بتاعك مش يستاهل الاستخدام. EXPLAIN ANALYZE بيوريك القرار ده بالأرقام، قبل ما تكتب CREATE INDEX تاني وتزوّد عبء الكتابة على الجدول من غير فايدة.
EXPLAIN ANALYZE: مش مجرد debug، ده عقد القرار
EXPLAIN لوحده بيطبع plan افتراضي بناءً على إحصائيات الـ planner. يعني تقدير، مش قياس فعلي. EXPLAIN ANALYZE بينفّذ الـ query فعلاً ويرجّعلك الأرقام الحقيقية: actual time، actual rows، عدد الـ buffers اللي اتقرت من الـ disk والـ cache. الفرق ده بيبان أوضح ما يكون لما الإحصائيات outdated أو لما الـ data distribution اتغير بعد كذا insert كبير.
مثال للمبتدئ على فكرة الـ planner: تخيّل سواق ديليفري عنده خريطة عمرها سنة. الخريطة بتقوله "الطريق ده فاضي" (تقدير). EXPLAIN ANALYZE معناه إنك ركبت العربية ودخلت الطريق فعلاً وسجّلت اتأخرت كام دقيقة. الخريطة القديمة بتغش أول ما الشارع يبقى مزدحم، وكل ما القرار يبقى مبني على خريطة قديمة، النتيجة بتبقى أسوأ.
المشكلة باختصار
الـ developers بيضيفوا indexes زي ما الـ ORM بيقترح: WHERE clause = index جديد. النتيجة الشائعة جداً: 6 indexes على جدول واحد، الكتابة بقت أبطأ بنسبة 40%، والـ SELECT لسه بطيء لأن الـ planner اختار Sequential Scan رغم وجود الـ index. الحل مش CREATE INDEX جديد، الحل تقرا الـ plan وتفهم ليه الـ planner اتخذ القرار ده.
قراءة المخرجات: الـ Nodes اللي لازم تركّز عليها
كل plan شجرة. كل عقدة (node) عملية. أهم 4 عقد هتشوفهم بشكل متكرّر:
- Seq Scan: قراءة كل صفوف الجدول صف صف. على جدول 50 ألف صف ممكن يكون أسرع من Index Scan. على جدول 12 مليون صف كارثة لو الـ filter بيرجّع نسبة صغيرة.
- Index Scan: قراءة عبر الـ B-Tree بعدها fetch من الـ heap. كويس لو النتائج قليلة (أقل من ~5% من الجدول).
- Bitmap Heap Scan: لما النتائج كثيرة لكن أقل من نص الجدول. الـ planner بيبني bitmap في الذاكرة لكل الصفحات اللي محتاجها ثم بيقراهم بترتيب على الـ disk.
- Nested Loop: مناسب لما الجدول الخارجي صغير (أقل من 1000 صف غالباً) والداخلي معاه index على الـ join key. لو الاتنين كبار، Hash Join أو Merge Join أفضل.
أهم سطر في كل عقدة هو: (actual time=X..Y rows=R loops=L). اضرب الزمن في loops علشان تعرف الزمن الفعلي اللي صرفته العقدة. الـ planner بيكذب لما يقدّر rows=50 وانت بتشوف actual rows=180000. الفرق ده لوحده مؤشر إن إحصائياتك محتاجة ANALYZE.
مثال عملي: query شكلها كويسة وفعلاً بطيئة
الـ schema: جدول orders فيه 12.4 مليون صف، عمود عليه B-Tree index قديم. السؤال: ارجّعلي طلبات عميل معيّن في آخر شهر.