تحسين استعلامات SQL باستخدام الفهارس: دليل عملي
قد يعمل الاستعلام بسرعة عندما تحتوي قاعدة البيانات على مئات الصفوف، ثم يصبح بطيئاً بعد نمو البيانات. لا يعني ذلك أن الحل هو إضافة فهرس إلى كل عمود. الفهرس بنية إضافية تساعد قاعدة البيانات على العثور على الصفوف، لكنه يحتاج مساحة وقد يزيد تكلفة عمليات الإضافة والتعديل. يشرح المقال كيف تفكر في الفهارس، وكيف تستخدم EXPLAIN QUERY PLAN في SQLite لفهم طريقة التنفيذ، ثم يوضح حالات يكون فيها الفهرس مفيداً وأخرى لا يغير فيها شيئاً. ستخرج من المثال بطريقة اختبار، لا بقائمة نصائح منفصلة عن الاستعلام الفعلي.
الفكرة الأساسية
يبحث الاستعلام من دون فهرس في عدد كبير من الصفوف حتى يجد النتائج المطابقة. عند إنشاء فهرس على عمود يستخدم في WHERE، يمكن لمحرك قاعدة البيانات الوصول إلى نطاق أصغر. فهرس مركب على أكثر من عمود مفيد عندما تتكرر الشروط بترتيب مناسب، لكنه ليس بديلاً عن قراءة خطة التنفيذ. إذا كان الاستعلام يعيد معظم صفوف الجدول، فقد لا يستحق استخدام الفهرس. كما يجب الانتباه إلى أن دالة على العمود، مثل LOWER(email)، قد تمنع الاستفادة من فهرس عادي ما لم توجد معالجة مناسبة.
مثال برمجي عملي
import sqlite3
connection = sqlite3.connect("shop.db")
connection.execute("""
CREATE INDEX IF NOT EXISTS idx_products_category_price
ON products(category, price_cents)
""")
plan = connection.execute("""
EXPLAIN QUERY PLAN
SELECT id, name, price_cents
FROM products
WHERE category = ? AND price_cents < ?
ORDER BY price_cents
""", ("كتب", 5000)).fetchall()
for row in plan:
print(row)
connection.close()
شرح المثال
ينشئ المثال فهرساً مركباً على category وprice_cents لأن الاستعلام يرشح بالتصنيف والسعر ثم يرتب بالسعر. تستخدم العلامات ? بدلاً من دمج القيم داخل النص، وهذا يحافظ على فصل البيانات عن الاستعلام. يعيد EXPLAIN QUERY PLAN وصفاً مختصراً للطريقة التي يخطط بها SQLite لتنفيذ العملية. إذا ظهر استخدام للفهرس فهذا مؤشر جيد، لكن لا يكفي وحده. قس الزمن على بيانات قريبة من الواقع، لأن عدد الصفوف وتوزيع القيم يغيران القرار.
ابدأ بتحديد الاستعلام الذي يسبب المشكلة، ثم قس زمنه قبل أي تغيير. افحص الأعمدة الموجودة في WHERE وJOIN وORDER BY، وبعدها جرّب فهرساً واحداً. شغل الخطة قبل وبعد إنشاء الفهرس وسجل الفرق. لا تضف فهارس متشابهة من دون حاجة، ولا تحذف فهرساً يستخدمه استعلام آخر. في بيئة الإنتاج نفذ تغييرات الفهرسة بعد فهم حجم الجدول وطبيعة عمليات الكتابة، لأن الفائدة في القراءة قد تقابلها كلفة في الإضافة.
طريقة العمل قبل كتابة الكود
قبل فتح محرر النصوص، اكتب النتيجة التي تريد الوصول إليها وحدد المدخلات والمخرجات. يساعد هذا التمرين على كشف الحالات الغامضة مبكراً، مثل قيمة ناقصة أو قائمة فارغة أو طلب لا يصل إلى الخادم. لا تحتاج إلى مخطط كبير للمثال التعليمي، لكنك تحتاج إلى أسماء واضحة وخطوات يمكن اختبارها واحدة بعد أخرى. عندما تتغير الفكرة أثناء الكتابة، عدل التصميم قبل إضافة شروط متفرعة يصعب تتبعها.
قسّم المشكلة إلى أجزاء صغيرة، واجعل كل جزء مسؤولاً عن قرار واحد قدر الإمكان. يمكن أن تكون الأجزاء دوال، أو مكونات، أو طبقات منفصلة بحسب التقنية. لا يعني التقسيم إنشاء ملفات كثيرة، بل يعني أن تعرف أين تبحث عندما يحدث الخطأ. سجّل الافتراضات المهمة بجملة قصيرة، مثل أن القائمة مرتبة أو أن السعر غير سالب، ثم تحقق منها في المكان المناسب بدلاً من الاعتماد على الذاكرة.
اختبار المثال في حالات مختلفة
لا تختبر المسار الطبيعي فقط. جرّب مدخلاً فارغاً، وقيمة أكبر من المتوقع، وعنصراً غير موجود، وطلباً يصل من دون البيانات المطلوبة. الحالات الحدية تكشف غالباً مشكلات الفهارس والأنواع وترتيب التنفيذ. اكتب النتيجة المتوقعة قبل تشغيل الكود، ثم قارنها بالنتيجة الفعلية. إذا كان الاختبار يمر بالصدفة، أضف شرطاً أو اختباراً أوضح، ولا تكتفِ بطباعة قيمة تبدو منطقية.
- تحقق من المدخلات قبل استخدامها في الحساب أو التخزين.
- أعد رسالة مفهومة ورمزاً مناسباً عند وقوع الخطأ.
- اجعل الاختبارات قابلة للإعادة من دون اعتماد على شبكة أو وقت متغير.
- راجع أثر التغيير على الأجزاء التي تستدعي الدالة أو المكون.
ملاحظات تتعلق بجودة الكود
تظهر جودة الحل في التفاصيل الصغيرة: اسم يشرح الغرض، ودالة لا تجمع مهاماً بعيدة، ورسالة خطأ لا تترك القارئ في حيرة. لا تحاول اختصار كل سطر، فالكود المقروء أفضل من تعبير قصير يحتاج إلى شرح طويل. وفي الوقت نفسه، لا تكرر القاعدة نفسها في أماكن كثيرة؛ انقلها إلى موضع واحد عندما يكون ذلك أوضح. راجع الملف بعد أن يعمل، لأن أول نسخة تركز عادةً على الوصول إلى النتيجة أكثر من قابلية الصيانة.
احتفظ بالإعدادات التي تختلف بين جهاز وآخر خارج الكود، ولا تضع كلمات مرور أو مفاتيح خاصة في المستودع. استخدم سجلات مناسبة أثناء التطوير، ثم راجع ما ينبغي حجبه في بيئة التشغيل. إذا تعامل البرنامج مع بيانات المستخدم، فافصل بين ما يحتاجه التطبيق وما يمكن الاحتفاظ به. هذه الممارسات لا تخص لغة واحدة، بل تقلل المشكلات عندما يكبر المشروع أو يعمل عليه أكثر من شخص.
متى تعرف أن الحل يحتاج إلى تطوير؟
يحتاج المثال التعليمي إلى طبقات إضافية عندما يدخل في نظام حقيقي: قاعدة بيانات، مستخدمون متعددون، مراقبة، اختبارات، وصلاحيات. لا تضف هذه الأجزاء قبل معرفة المشكلة التي تحلها، لكن لا تنقل الكود التجريبي إلى الإنتاج كما هو. راقب حجم البيانات، وعدد الطلبات، ومصدر المدخلات، وما إذا كان الفشل يجب أن يعيد العملية أو يوقفها. اكتب قرارك في مستند صغير أو تعليق يشرح السبب، حتى لا يضطر الفريق إلى تخمينه لاحقاً.
إذا وجدت أن الخطأ يتكرر في أكثر من مكان، فابحث عن قاعدة مشتركة. وإذا أصبح التعديل في ملف صغير يؤثر في ملفات كثيرة، فراجع حدود المسؤوليات. لا توجد بنية واحدة صحيحة لكل مشروع، لكن توجد أسئلة تساعدك على اختيار بنية مناسبة: من يملك البيانات؟ من يغيرها؟ ماذا يحدث عند الفشل؟ وكيف يمكن اختبار الجزء من دون تشغيل النظام كله؟
أخطاء ينبغي تجنبها
يظن بعض المطورين أن كل عمود يحتاج فهرساً، بينما يضيف ذلك وقتاً ومساحة من دون فائدة. يخطئ آخرون في وضع العمود الأقل انتقائية أولاً في فهرس مركب، أو في تجاهل ترتيب الأعمدة المطلوب في الاستعلام. لا تقارن بين استعلامين مختلفين وتستنتج أن الفهرس نجح. استخدم بيانات مماثلة للواقع، وانتبه إلى أن وجود فهرس لا يضمن استخدامه في كل خطة.
خطوة تالية مناسبة
بعد فهم الفهارس، تعلم الفرق بين فهرس يغطي الأعمدة المطلوبة وفهرس يحتاج إلى العودة للجدول، ثم راجع تصميم الاستعلام نفسه. قد يكون تقليل الأعمدة المطلوبة أو تجنب SELECT * أكثر فائدة من فهرس جديد. إذا كان التطبيق يستخدم PostgreSQL أو MySQL، فتعرف إلى أدوات تحليل الخطة الخاصة بالنظام، لأن التفاصيل تختلف رغم بقاء الفكرة العامة مشتركة.
كيف تقيس التحسن فعلياً؟
لا يكفي أن يصبح الاستعلام أسرع في جهازك أثناء اختبار واحد. نفذ الاستعلام قبل الفهرس وبعده على حجم بيانات قريب من الاستخدام المتوقع، وسجل الزمن وعدد الصفوف التي قرأها المخطط. كرر القياس عندما تختلف قيمة البحث، لأن بعض القيم تعيد صفوفاً كثيرة وبعضها يعيد صفاً واحداً. راقب أيضاً زمن الكتابة؛ فكل فهرس يحتاج إلى تحديث عند الإضافة والتعديل والحذف. إذا كان الجدول يتلقى كتابات كثيرة واستعلاماته محدودة، فقد يكون فهرس إضافي عبئاً لا يستحقه. القرار الجيد يوازن بين سرعة القراءة، وتكلفة التخزين، وزمن التحديث، بدلاً من إضافة فهرس لكل عمود يظهر في جملة WHERE.
الخلاصة
بعد فهم الفهارس، تعلم الفرق بين فهرس يغطي الأعمدة المطلوبة وفهرس يحتاج إلى العودة للجدول، ثم راجع تصميم الاستعلام نفسه. قد يكون تقليل الأعمدة المطلوبة أو تجنب SELECT * أكثر فائدة من فهرس جديد. إذا كان التطبيق يستخدم PostgreSQL أو MySQL، فتعرف إلى أدوات تحليل الخطة الخاصة بالنظام، لأن التفاصيل تختلف رغم بقاء الفكرة العامة مشتركة. يوضح هذا الموضوع كيف يتحول مفهوم نظري إلى خطوات يمكن تشغيلها وفحصها.
ابدأ بتطبيق المثال على ملف صغير، ثم غيّر مدخلاً واحداً وراقب النتيجة. بعد ذلك أضف حالة فشل واكتب اختباراً لها، ثم انقل الفكرة إلى مشروعك الحقيقي بحذر. عندما تفهم سبب كل خطوة، ستستطيع تغيير الأدوات أو اللغة من دون فقدان المفهوم. البرمجة تتحسن بالمحاولات القصيرة والمراجعة المستمرة، لا بنسخ كود طويل من دون معرفة ما الذي يحميه أو ما الذي قد يكسره.