إذا كان تطبيقك بطيئًا وقد قمت بالفعل بتحسين الصور وتفعيل التخزين المؤقت وضبط إعدادات الخادم، فهناك احتمال كبير أن المشكلة الحقيقية تكمن بهدوء في قاعدة البيانات لديك. وتحديدًا، في الفهارس (indexes)، أو في غيابها.
إضافة الفهرس المناسب على العمود المناسب هو أحد تلك الحلول النادرة التي يمكن أن تُخفّض زمن الاستعلام من 800 ميلي ثانية إلى 8 ميلي ثوانٍ فقط. هذا ليس مبالغة. يحدث هذا طوال الوقت، وبمجرد أن تفهم السبب، ستبدأ برؤية هذه الفرص في كل مكان.
ما الذي يفعله الفهرس فعليًا
تخيل جدولًا في قاعدة البيانات بدون فهرس وكأنه دليل هاتف غير مرتب أبجديًا. للعثور على "سميث"، ستضطر لقراءة كل إدخال من الصفحة الأولى إلى الأخيرة. يُسمى هذا بالمسح الكامل للجدول (full table scan)، وهذا بالضبط ما تفعله قاعدة البيانات عندما لا تستطيع استخدام فهرس.
الفهرس هو بنية بيانات منفصلة، عادة ما تكون شجرة B-tree، تحتفظ بمرجع مرتب للقيم الموجودة في عمود (أو مجموعة أعمدة) إلى جانب مؤشرات تشير إلى مكان وجود الصفوف الفعلية. بدلاً من مسح مليون صف، تقفز قاعدة البيانات مباشرة تقريبًا إلى الصفوف التي تحتاجها.
إليك الفرق عمليًا. لنفترض أن لديك جدول `users` يحتوي على مليون صف وتُنفذ:
SELECT * FROM users WHERE email = 'jane@example.com';
بدون فهرس على عمود `email`، تتحقق قاعدة البيانات من كل صف. مع وجود فهرس، تُنفذ عملية بحث تقترب من O(log n) بدلاً من O(n). في جدول بمليون صف، هذا هو الفرق بين فحص مليون صف وفحص حوالي 20 صفًا فقط.
كيفية إيجاد الأعمدة التي تحتاج فعلاً إلى فهرسة
ليس كل عمود يستحق الفهرسة. فهرسة كل شيء تُبطئ فعليًا عمليات الكتابة، لأن كل عملية إدخال أو تحديث يجب أن تُحدّث كل فهرس على الجدول أيضًا. الهدف هو تحسين قاعدة البيانات بطريقة استراتيجية عبر اختيار الفهارس بعناية.
انظر إلى جمل WHERE وJOIN وORDER BY
هذه هي الأماكن الثلاثة الأكثر أهمية للفهارس:
- الأعمدة المستخدمة في تصفية WHERE، خصوصًا في الجداول الكبيرة
- الأعمدة المستخدمة لربط الجداول عبر JOIN، مثل المفاتيح الأجنبية
- الأعمدة المستخدمة في ORDER BY أو GROUP BY، لأن ترتيب البيانات المفهرسة مسبقًا أقل تكلفة بكثير
استخدم EXPLAIN قبل التخمين
لا تقم بالفهرسة بشكل عشوائي. نفّذ أمر EXPLAIN (أو EXPLAIN ANALYZE في PostgreSQL) على استعلاماتك البطيئة أولاً. في MySQL، إذا أظهرت خطة الاستعلام type: ALL فهذا يعني أنك أمام مسح كامل للجدول. هذه هي الإشارة التي تحتاجها.
على سبيل المثال:
EXPLAIN SELECT * FROM orders WHERE customer_id = 4821 AND status = 'pending';
إذا أعاد هذا مسحًا كاملاً لجدول `orders` المكوّن من 500,000 صف، فهذا مرشح قوي لإنشاء فهرس مركّب على `(customer_id, status)`.
الفهارس المركبة وترتيب الأعمدة مهمّان
من الأخطاء الشائعة إنشاء فهارس أحادية العمود بينما كان الفهرس المركب سيخدم الاستعلام بشكل أفضل بكثير. إذا كنت تُصفّي البيانات باستمرار باستخدام عمودين معًا، فقم بفهرستهما معًا، وبنفس ترتيب استخدامهما.
ترتيب الأعمدة في الفهرس المركب ليس عشوائيًا. الفهرس على `(customer_id, status)` يساعد الاستعلامات التي تُصفّي باستخدام `customer_id` وحده أو باستخدام العمودين معًا. لكنه لن يساعد استعلامًا يُصفّي باستخدام `status` فقط. فكّر في الأمر مثل دليل هاتف مرتب حسب اسم العائلة ثم الاسم الأول. يمكنك إيجاد "سميث" بسهولة، بل وإيجاد "سميث، جون" بشكل أسرع، لكن لا يمكنك إيجاد كل من يُدعى "جون" بسرعة عبر جميع أسماء العائلات.
مثال حقيقي: انخفاض بنسبة 90 بالمئة
إليك نمطًا نراه باستمرار. موقع تجارة إلكترونية لديه جدول `orders` يحتوي على 2 مليون صف. استعلام في لوحة التحكم يُصفّي الطلبات الحديثة حسب معرّف المتجر ونطاق التاريخ:
SELECT * FROM orders WHERE store_id = 55 AND created_at > '2024-01-01' ORDER BY created_at DESC;
بدون فهرس، يستغرق هذا الاستعلام حوالي 1.2 ثانية تحت الحمل. فهو يمسح الجدول بالكامل، ثم يُصفّي في الذاكرة، ثم يُرتّب. بعد إضافة فهرس مركّب على `(store_id, created_at)`، انخفض نفس الاستعلام إلى حوالي 90 ميلي ثانية. هذا انخفاض بنسبة 92 بالمئة، وتطلّب سطرًا واحدًا فقط من SQL:
CREATE INDEX idx_store_created ON orders (store_id, created_at);
بدون أي تغيير في الكود، ولا عتاد جديد، ولا طبقة تخزين مؤقت. مجرد فهرس صحيح على الأعمدة الصحيحة.
احذر من السلبيات
الفهارس ليست مجانية. كل فهرس يضيف عبئًا على عمليات الكتابة ويستهلك مساحة تخزين. الجدول الذي يحتوي على عشرة فهارس سيكون إدخال وتحديث بياناته أبطأ بشكل ملحوظ من جدول يحتوي على فهرسين مختارين بعناية. إليك بعض الإرشادات العملية:
- لا تُفهرس الأعمدة ذات القيم القليلة التمايز مثل حقل منطقي `is_active` وحده، لأن قاعدة البيانات غالبًا لن تستطيع تضييق النتائج كثيرًا
- راجع دوريًا الفهارس غير المستخدمة. معظم قواعد البيانات يمكنها الإبلاغ عن الفهارس التي لم تُستخدم منذ أسابيع
- تجنّب فهرسة الأعمدة التي تتغيّر باستمرار إذا كانت فائدة القراءة لا تفوق تكلفة الكتابة
أين يأتي دور الاستضافة في كل هذا
الفهرسة الجيدة تُنجز معظم المهمة، لكن الخادم الذي يقف خلف كل ذلك لا يزال مهمًا. بطء إدخال/إخراج القرص، أو ضغط الذاكرة، أو تنافس قاعدة البيانات مع مستأجرين آخرين على الموارد في خادم مشترك مزدحم، كل هذا قد يُلغي الكثير من عملك في الفهرسة. على بيئة VPS ذات موارد مناسبة، تستفيد عمليات البحث في الفهرس فعليًا من إدخال/إخراج سريع ومخصص بدلاً من الانتظار خلف حركة مرور شخص آخر.
من المفيد أيضًا مراقبة أداء استعلاماتك مع مرور الوقت، لأن البيانات تنمو وأنماط الاستعلام تتغيّر. مراقبة الخادم التي تتتبع حمل قاعدة البيانات إلى جانب المعالج والذاكرة يمكن أن تكتشف استعلامًا يتراجع أداؤه بهدوء قبل أن يتحول إلى مشكلة حقيقية.
كتبنا المزيد عن الصورة الأوسع للاستعلامات البطيئة في لماذا تُعد استعلامات قاعدة البيانات البطيئة عنق الزجاجة الخفي في معظم تطبيقات الويب، وإذا كنت تستخدم WordPress تحديدًا، فإن مقال تنظيف قاعدة البيانات لـ WordPress يتناول جانبًا مرتبطًا يستحق القراءة.
الخلاصة
استضافة تحسين قاعدة البيانات لا تقتصر فقط على اختيار خادم سريع. بل تتعلق بالجمع بين بنية تحتية جيدة وقرارات ذكية في تصميم المخطط، والفهرسة هي القرار الأكثر تأثيرًا الذي يمكنك اتخاذه. قبل أن تلجأ إلى خادم أكبر أو طبقة تخزين مؤقت جديدة، نفّذ EXPLAIN على أبطأ استعلاماتك وانظر إذا كان فهرس واحد موضوع بشكل صحيح قد يقوم بالمهمة بدلاً من ذلك. غالبًا ما ستفاجأ بأنه سيفعل.