B-Tree Indexes في PostgreSQL للمبتدئ: نزّل query من 14 ثانية لـ 38 ميكرو ثانية بسطر واحد
مستوى المقال: مبتدئ
لو عندك جدول users فيه 5 مليون صف، وبتعمل SELECT * FROM users WHERE email = 'ahmed@haies.com' بياخد 14 ثانية، PostgreSQL مش بطيء. هو بيقرأ كل صف من أول الجدول لآخره عشان يلاقي الصف اللي إنت طلبته. سطر CREATE INDEX واحد بينزّل نفس الـ query لـ 38 ميكرو ثانية على نفس الجهاز — يعني فرق 374,000 ضعف.
المشكلة باختصار
كل جدول في PostgreSQL بيتخزّن على القرص كصفوف ورا بعض في ملفات بحجم 1GB لكل واحد. لمّا بتكتب WHERE email = ... بدون index، الـ DB بتعمل العملية اللي اسمها Sequential Scan: بتقرأ كل صف، بتقارن قيمة الـ email، بتقرر إذا كان matches ولا لا. على 5 مليون صف، ده معناه قراءة حوالي 1.2 جيجا من القرص لـ query واحد بسيط.
المشكلة بتظهر في الإنتاج بس. الـ dashboard بيرد في 200 مللي ثانية على بيانات الـ test (10 آلاف صف لأن الجدول كله بيتحمّل في الـ RAM)، وبيرد في 14 ثانية مع زبون حقيقي عنده مليون عميل. السيرفر مش غلطان، الـ ORM مش غلطان — انت ناسي تعمل index على العمود اللي بتفلتر بيه.
المثال البسيط: أمين المكتبة
تخيّل إنك دخلت مكتبة فيها 5 مليون كتاب، الكتب متراصّة على الرفوف بترتيب اللي اشترته المكتبة (يعني عشوائي تمامًا بالنسبة لك). طلبت من أمين المكتبة كتاب باسم مؤلف معيّن. هو ميعرفش الكتاب فين بالظبط، فهيبدأ يمشي على كل رف، يبص على كل كتاب، يقرا اسم المؤلف، ويقارن. ممكن يلاقيه أول 10 دقايق لو الكتاب على أول رف، وممكن آخر النهار لو على آخر رف. ده بالظبط Sequential Scan.
الـ Index هو فهرس منفصل، صغير، مرتّب أبجديًا على اسم المؤلف، ومكتوب جنب كل اسم رقم الرف بالظبط. أمين المكتبة بدل ما يدوّر، بيفتح الفهرس، يلاقي الاسم في 3 ثواني، يروح على الرف اللي مكتوب جنب الاسم، ياخد الكتاب. الفهرس نفسه ممكن يبقى كتاب حجمه 200 صفحة بس، بدل ما يدوّر في 5 مليون كتاب. الفرق مش 10x ولا 100x — الفرق ممكن يبقى 100,000x أو أكتر.
التعريف العلمي
الـ B-Tree Index في PostgreSQL هو بنية بيانات شجرية متوازنة (self-balancing tree) مبنية على ورقة Bayer وMcCreight اللي اتنشرت في Acta Informatica سنة 1972. كل عقدة (node) في الشجرة فيها مفاتيح مرتّبة، وأي بحث بيمشي من الجذر للورقة في عمق log₂(n). على 5 مليون صف، ده تقريبًا 23 خطوة بحث بدل 5 مليون مقارنة.
التوثيق الرسمي على postgresql.org/docs/16/indexes-types.html بيوضّح إن B-Tree هو الـ default وأنسب نوع لـ: المساواة (=)، المقارنة (<، >، BETWEEN)، الـ ORDER BY، والـ LIKE اللي بيبدأ بـ prefix ثابت زي LIKE 'ahmed%'.
الكود الفعلي — جرّبه دلوقتي
الكود ده شغّال زي ما هو على PostgreSQL 16+ وميحتاجش setup إضافي. افتح psql ونفّذ: