قواعد البيانات والتخزين

الفهرسة

18 دقيقة الدرس 4 من 10

الفهرسة

مع نمو أي جدول في قاعدة بيانات إنتاجية، يصبح المسح الكامل للجدول بطيئًا بشكل مؤلم. مسح 50 مليون صف للعثور على مستخدم واحد بالبريد الإلكتروني ليس خيارًا عندما يجب أن يستجيب API الخاص بك في أقل من 20 ميلي ثانية. الفهارس (Indexes) هي الأداة الأساسية التي تستخدمها قواعد البيانات للعثور على الصفوف دون قراءة كل صفحة على القرص. فهم آلية عملها — وما تكلفه — أمر ضروري لكل مهندس يصمم أنظمة على نطاق واسع.

ما هو الفهرس؟

الفهرس هو هيكل بيانات منفصل يُحتفظ به بجانب الجدول، يربط قيم الأعمدة بالموقع الفعلي (رقم الصفحة وإزاحة الصف) للصفوف المطابقة. فكّر فيه كفهرس الكتاب في نهايته: بدلاً من قراءة كل صفحة للعثور على "B-tree"، تبحث في الفهرس وتنتقل مباشرة إلى الصفحة الصحيحة.

بدون فهرس، تجري قاعدة البيانات مسحًا كاملاً للجدول — تقرأ كل صف لتقييم شرط WHERE. مع وجود فهرس على العمود المبحوث، تنفذ بحثًا في الفهرس — تتنقل في هيكل الفهرس في خطوات O(log n) وتجلب الصفوف المطابقة فحسب.

Full scan vs index seek Without Index — Full Table Scan Row 1 — user_id=1, email=alice@… Row 2 — user_id=2, email=bob@… Row 3 — user_id=3, email=carol@… Row 4 — user_id=4, email=dave@… ✓ Row 5 — user_id=5, email=eve@… ⋯ reads ALL rows ⋯ O(n) — scans every row With Index — Index Seek B-Tree Root Node Leaf: dave@ → Row 4 Row 4 — dave@ ✓ Navigates ~log₂(n) tree levels O(log n) — jumps directly
بدون فهرس تقرأ قاعدة البيانات كل صف (O(n))؛ مع فهرس B-tree تتنقل عبر مستويات قليلة للوصول للصف المطابق (O(log n)).

الـ B-Tree: كيف تعمل معظم الفهارس

يعتمد B-tree (الشجرة المتوازنة) على هيكل البيانات الذي يُشكّل الفهرس الافتراضي في PostgreSQL وMySQL (InnoDB) وOracle وSQL Server. هو هيكل متوازن ذاتيًا، بمعنى أن كل ورقة تقع على نفس العمق بغض النظر عن عدد الصفوف المُدرَجة أو المحذوفة. جدول بـ50 مليون صف يمتلك B-tree بعمق 4-5 مستويات تقريبًا — وهذا يعني أن العثور على أي صف لا يتطلب أكثر من 4-5 قراءات صفحة.

يتكون B-tree من ثلاثة أنواع من العقد:

  • العقدة الجذر (Root node) — نقطة الدخول الوحيدة أعلى الشجرة.
  • العقد الداخلية (Internal nodes) — توجّه عمليات البحث نحو الشجرة الفرعية الصحيحة عبر مقارنة قيم المفاتيح.
  • عقد الأوراق (Leaf nodes) — تخزن مفتاح الفهرس الفعلي مع مؤشر لصف الجدول (معرّف الصف أو رقم الصفحة+الفتحة). ترتبط العقد الورقية ببعضها في قائمة مزدوجة الارتباط لتمكين مسح النطاقات (مثل WHERE created_at BETWEEN …) بكفاءة دون العودة إلى الجذر.
B-tree index structure Root Node 50 | 200 | 600 Internal 10 | 30 | 45 Internal 100 | 150 | 180 Internal 300 | 450 | 580 Leaf 1 → row ptr 5 → row ptr Leaf 15 → row ptr 22 → row ptr Leaf 60 → row ptr 90 → row ptr Leaf 210 → row ptr 250 → row ptr Leaf 620 → row ptr 700 → row ptr ← Leaf nodes linked for range scans →
هيكل فهرس B-tree: العقدة الجذر والعقد الداخلية توجّه عمليات البحث؛ عقد الأوراق تحمل مؤشرات المفتاح-للصف وترتبط ببعضها لتسهيل مسح النطاقات.

أنواع الفهارس

إلى جانب B-tree القياسي، تقدم قواعد البيانات عدة أنواع متخصصة من الفهارس:

  • فهرس Hash — يخزن خريطة هاش غير مرتبة من المفتاح إلى الصف. سريع جدًا لعمليات البحث عن تطابق تام (O(1))، لكنه عديم الفائدة لاستعلامات النطاق أو الترتيب.
  • الفهرس المركّب (Composite index) — B-tree مبني على عمودين أو أكثر. يجب أن تستخدم الاستعلامات البادئة اليسرى للفهرس. فهرس على (last_name, first_name) يفيد الاستعلام الذي يصفّي على last_name وحده، لكنه لا يفيد من يصفّي على first_name وحده.
  • الفهرس الشامل (Covering index) — فهرس يحتوي على جميع الأعمدة التي يحتاجها الاستعلام، بحيث لا تضطر قاعدة البيانات لقراءة جدول الكومة (heap) أصلًا. يُنجز الاستعلام كاملاً من الفهرس — وهو مكسب كبير للجداول ذات معدل القراءة العالي.
  • الفهرس الجزئي (Partial index) — فهرس على مجموعة فرعية من الصفوف (مثل WHERE status = 'active'). أصغر حجمًا وأسرع بكثير حين تستعلم دائمًا عن السجلات النشطة فحسب.
  • فهرس النص الكامل (Full-text index) — فهرس مقلوب مبني للبحث بالكلمات المفتاحية داخل أعمدة النص. محرّك مختلف عن B-trees.

المقايضة: التكلفة في الكتابة والمساحة

الفهارس ليست مجانية. كل فهرس تضيفه يفرض ثلاثة تكاليف:

  1. مساحة إضافية على القرص — B-tree على جدول بـ50 مليون صف مع عمود VARCHAR(255) للبريد الإلكتروني يمكن أن يستهلك عدة غيغابايت بسهولة.
  2. عبء الكتابة — عند كل INSERT أو UPDATE أو DELETE، يجب على قاعدة البيانات تحديث كل فهرس يغطي الأعمدة المُعدَّلة. جدول بـ8 فهارس يتطلب ضعف الجهد تقريبًا مقارنةً بجدول بلا فهارس. في الجداول ذات الكتابة المكثفة (مثل جداول أحداث السلاسل الزمنية التي تستوعب 100 ألف صف/ثانية) يمكن أن تُرهق الفهارس الزائدة عمليات الإدخال/الإخراج.
  3. التنافس على الأقفال — يمكن أن تصبح صفحات الفهرس نقاط ازدحام، خاصة في أقصى يمين ورقة متسلسل متزايد باستمرار.
الإفراط في إضافة الفهارس مشكلة حقيقية. من الأنماط الشائعة الخاطئة إضافة فهرس لكل استعلام كُتب. في الجداول ذات الكتابة المكثفة، يُقلّل هذا الإنتاجية بشكل كبير. دائمًا قِس الأداء. استخدم EXPLAIN ANALYZE في PostgreSQL أو EXPLAIN في MySQL للتأكد من استخدام الفهرس فعليًا قبل إضافته، وراجع دوريًا الفهارس غير المستخدمة واحذفها.

الفهارس المجمّعة مقابل غير المجمّعة

يحدد الفهرس المجمّع (Clustered index) الترتيب الفعلي للصفوف على القرص. في InnoDB (المحرك الافتراضي في MySQL) يكون المفتاح الأساسي دائمًا فهرسًا مجمّعًا — الجدول هو شجرة B-tree نفسها. للوصول إلى صف عبر المفتاح الأساسي تكفي عملية اجتياز واحدة للشجرة. أما الفهارس الثانوية في InnoDB فتخزن قيمة المفتاح الأساسي كمؤشر للصف، لذا تتطلب عمليتَي اجتياز: الأولى في الفهرس الثانوي للحصول على المفتاح الأساسي، ثم الثانية في الفهرس المجمّع لجلب الصف الكامل.

اختر المفاتيح الأساسية بعناية. في InnoDB، استخدام UUID (قيم عشوائية 128 بت) كمفاتيح أساسية يُفتّت B-tree المجمّع لأن الصفوف الجديدة تُدرج بشكل عشوائي عبر جميع صفحات الأوراق. المفاتيح الصحيحة المتسلسلة (AUTO_INCREMENT أو BIGSERIAL) أو UUIDs المرتبة زمنيًا (UUIDv7) تُبقي الإدراج عند أقصى يمين الورقة، مما يُقلل انقسامات الصفحات وتضخيم الكتابة.

إرشادات عملية

  • أضف فهرسًا على كل عمود مفتاح خارجي (Foreign Key) — أعمدة FK بلا فهرس تُسبب مسحًا كاملاً عند كل ضم (join).
  • أضف فهرسًا على الأعمدة المستخدمة في جمل WHERE وJOIN ON وORDER BY للاستعلامات البطيئة المتكررة.
  • فضّل الفهارس المركّبة على الفهارس الفردية المتعددة حين تصفّي الاستعلامات دائمًا على مجموعة ثابتة من الأعمدة.
  • استخدم الفهارس الجزئية للمجموعات المصفّاة ذات الكثافة المنخفضة (مثل WHERE processed = false).
  • راقب مخطط الاستعلام: إذا اختارت قاعدة البيانات المسح الكامل رغم وجود فهرس، فربما إحصائيات الجدول قديمة — شغّل ANALYZE لتحديثها.
ابدأ دائمًا بـ EXPLAIN. قبل إضافة أي فهرس، شغّل EXPLAIN ANALYZE <استعلامك> في PostgreSQL (أو EXPLAIN FORMAT=JSON في MySQL) وانظر إلى الصفوف الفعلية التي مُسحت، وعقدة الخطة المختارة، ووقت التنفيذ. هذه العادة البسيطة التي تستغرق 30 ثانية توفر ساعات من التخمين وتمنع إضافة فهارس غير ضرورية.

ملخص

الفهارس هي أكثر أدوات الضبط تأثيرًا في قواعد البيانات العلائقية. يُحوّل فهرس B-tree مسحًا بتعقيد O(n) إلى بحث بتعقيد O(log n) عبر الحفاظ على هيكل بيانات متوازن ومرتب مع مؤشرات على مستوى الأوراق. الثمن هو مساحة قرص إضافية وتضخيم الكتابة — يجب تحديث كل فهرس عند التعديل. فن الفهرسة هو إيجاد أصغر مجموعة من الفهارس التي تُلبي أنماط القراءة دون الإضرار بإنتاجية الكتابة. تحقق دائمًا من مخطط الاستعلام قبل التغييرات وبعدها.