المستوى: مبتدئ
لو شغّلت SELECT id, total FROM orders WHERE customer_email = 'x@y.com' على جدول فيه 2 مليون صف ولقيته بياخد 8 ثواني، المشكلة مش في السيرفر ولا في PostgreSQL. المشكلة إن قاعدة البيانات بتقرا الجدول كله سطر سطر علشان تلاقيلك صف واحد. الـ Index بيحل ده، وفي الغالب بيوصل بالاستعلام من 8 ثواني لـ 12ms من غير ما تغيّر سطر واحد في كود التطبيق.
Database Index بالعربي: من 8 ثواني لـ 12ms بدون لمس الكود
المشكلة باختصار
قواعد البيانات بتخزّن الصفوف في ملفات على القرص بترتيب الإدخال، مش بترتيب البحث. لما تطلب صف بشرط WHERE، الـ query planner عنده خيارين: يقرا الجدول كله (Sequential Scan)، أو يستخدم Index لو موجود على العمود ده. على جدول صغير، Sequential Scan أسرع لأن قراءة كل البلوكات في مرة واحدة أوفر من قفز عشوائي. على جدول أكبر من 50 ألف صف، الفرق بيبدأ يبان: ثواني بدل ميلي ثواني، ومع نمو البيانات الفجوة بتكبر بشكل مش خطي.
الـ Index بمثال بسيط
تخيل عندك كتاب طبخ بـ 600 صفحة، وعايز توصفة "الكشري". في غياب فهرس، هتقلّب الكتاب صفحة صفحة من الأول لحد ما تلاقيها — ممكن تاخد دقيقتين. مع وجود فهرس في آخر الكتاب مرتب أبجديًا، بتفتح حرف "ك"، تلاقي "كشري — صفحة 247"، تفتحها مباشرة، انتهيت في 5 ثواني.
الـ Index في قاعدة البيانات بنفس المنطق: هيكل بيانات منفصل بيخزّن (القيمة → موقع الصف على القرص)، مرتب بطريقة بتسمح بالبحث بسرعة لوغاريتمية بدل خطية. الكتاب بيكبر، الفهرس بيكبر بشكل أبطأ بكتير، فالبحث بيفضل سريع.
التعريف العلمي
الـ Index الافتراضي في PostgreSQL وMySQL هو B-tree Index — اختصار Balanced Tree. ده شجرة متعددة الفروع متوازنة، بتقسّم نطاق القيم على عدة مستويات. لجدول فيه مليون صف، عمق الشجرة بيكون 3 إلى 4 مستويات بس. يعني بدل ما تقرا مليون صف، بتقرا 4 صفحات فهرس + صفحة بيانات = ~5 قراءات قرص بدل مليون.
الفرضية المهمة هنا: الـ B-tree بيشتغل لما تكون بتدوّر بمساواة (=) أو نطاق (BETWEEN, <, >) أو بداية نص (LIKE 'ahmed%'). لكنه ميشتغلش لو بتدوّر بنهاية نص (LIKE '%example.com') — ده محتاج Index من نوع تاني زي GIN مع pg_trgm.
مثال تنفيذي مع EXPLAIN ANALYZE
افترض جدول orders فيه 2 مليون صف، وعمود customer_email بدون Index. خد القياس قبل أي تعديل:
-- 1) قياس قبل الـ Index
EXPLAIN ANALYZE
SELECT id, total
FROM orders
WHERE customer_email = 'ahmed@example.com';
-- مخرج تقريبي:
-- Seq Scan on orders (cost=0.00..58420.00 rows=1 width=24)
-- Filter: (customer_email = 'ahmed@example.com')
-- Rows Removed by Filter: 1999999
-- Execution Time: 7843.214 ms