الفهارس في MySQL: CREATE INDEX و EXPLAIN
متجرنا فيه ستة منتجات وخمسة طلبات، وكل استعلام ينتهي في لحظة. لكن المتاجر الناجحة تكبر: مئة ألف منتج، وملايين الطلبات. عندها يتحول استعلام بسيط إلى انتظار طويل، والسبب في أغلب الحالات غياب الفهرس (Index) المناسب. في هذا الدرس ستتعلم كيف تعمل الفهارس في MySQL، وكيف تستخدم EXPLAIN لترى ما تفعله قاعدة البيانات فعلاً.
ما هو الفهرس؟
Section titled “ما هو الفهرس؟”بدون فهرس، لتنفيذ WHERE stock = 0 تقرأ MySQL الجدول صفاً صفاً من أوله لآخره. هذا اسمه الفحص الكامل (Full Table Scan).
الفهرس مثل فهرس آخر الكتاب: قائمة مرتبة بالكلمات، وبجانب كل كلمة رقم صفحتها. بدل تقليب 500 صفحة، تبحث في القائمة المرتبة وتقفز إلى الصفحة مباشرة. فهرس MySQL قائمة مرتبة بقيم عمود، ومع كل قيمة مكان صفها.
فهارس موجودة من البداية
Section titled “فهارس موجودة من البداية”SHOW INDEX FROM orders;الناتج (أهم الأعمدة):
| Table | Non_unique | Key_name | Column_name |
|---|---|---|---|
| orders | 0 | PRIMARY | id |
| orders | 1 | customer_id | customer_id |
| orders | 1 | product_id | product_id |
لم ننشئ أي فهرس، ومع ذلك عندنا ثلاثة:
PRIMARY: المفتاح الأساسي مفهرس تلقائياً، وNon_unique = 0تعني أنه لا يقبل التكرار.customer_idوproduct_id: محرك InnoDB ينشئ فهرساً لكل مفتاح أجنبي تلقائياً، لأن الربطJOINيحتاجه دائماً.
EXPLAIN: كيف ستنفذين هذا؟
Section titled “EXPLAIN: كيف ستنفذين هذا؟”ضع EXPLAIN قبل أي استعلام، فتعرض MySQL خطة التنفيذ بدل النتيجة:
EXPLAIN SELECT * FROM products WHERE stock = 0;الناتج (أهم الأعمدة):
| table | type | possible_keys | key | rows |
|---|---|---|---|---|
| products | ALL | NULL | NULL | 6 |
type = ALL: فحص كامل للجدول. هذه أسوأ قيمة.key = NULL: لا فهرس مستخدَم.rows = 6: العدد التقديري للصفوف التي ستُقرأ: كل الجدول.
إنشاء فهرس
Section titled “إنشاء فهرس”CREATE INDEX idx_products_stock ON products (stock);
EXPLAIN SELECT * FROM products WHERE stock = 0;الناتج (أهم الأعمدة):
| table | type | possible_keys | key | rows |
|---|---|---|---|---|
| products | ref | idx_products_stock | idx_products_stock | 1 |
تغيّر كل شيء دون تعديل الاستعلام نفسه: type أصبحت ref (بحث بالفهرس)، و key تعرض اسم الفهرس المستخدم، و rows نزلت من 6 إلى 1. في جدول من مليون منتج، هذا الفرق بين قراءة مليون صف وقراءة حفنة صفوف.
قيم type الشائعة من الأفضل للأسوأ:
| type | المعنى |
|---|---|
const |
صف واحد بالمفتاح الأساسي أو فهرس فريد، مثل WHERE id = 3 |
ref |
بحث بالمساواة في فهرس عادي |
range |
مدى في فهرس: > و < و BETWEEN و LIKE 'نص%' |
ALL |
فحص كامل للجدول |
الفهرس الفريد
Section titled “الفهرس الفريد”CREATE UNIQUE INDEX idx_products_name ON products (name);
INSERT INTO products (name, category, price)VALUES ('مجموعة أقلام', 'أدوات مدرسية', 20);الناتج: ERROR 1062 (23000): Duplicate entry 'مجموعة أقلام' for key 'products.idx_products_name'
الفهرس الفريد يسرّع البحث بالاسم ويمنع تكراره في الوقت نفسه. مناسب للبريد الإلكتروني واسم المستخدم ورمز المنتج.
الفهرس المركّب
Section titled “الفهرس المركّب”صفحة «طلباتي» في المتجر تعرض طلبات عميل معيّن في فترة معيّنة. فهرس واحد على العمودين معاً يخدمها:
CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date);
EXPLAIN SELECT * FROM ordersWHERE customer_id = 1 AND order_date >= '2026-11-01';الناتج (أهم الأعمدة):
| table | type | key | rows |
|---|---|---|---|
| orders | range | idx_orders_customer_date | 1 |
لكن ترتيب الأعمدة مهم. الفهرس مرتب برقم العميل أولاً، ثم بالتاريخ داخل كل عميل، مثل دليل هاتف مرتب باسم العائلة ثم الاسم الأول. البحث بالتاريخ وحده لا يستفيد منه:
EXPLAIN SELECT * FROM orders WHERE order_date >= '2026-11-01';الناتج (أهم الأعمدة):
| table | type | key | rows |
|---|---|---|---|
| orders | ALL | NULL | 5 |
القاعدة: الفهرس المركّب يُستخدم من اليسار: customer_id وحده، أو customer_id مع order_date.
كيف تعطّل الفهرس دون أن تدري
Section titled “كيف تعطّل الفهرس دون أن تدري”أنشأنا فهرساً على التاريخ وحده: CREATE INDEX idx_orders_date ON orders (order_date);. والآن طريقتان لجلب طلبات سنة 2025:
EXPLAIN SELECT * FROM orders WHERE YEAR(order_date) = 2025;الناتج (أهم الأعمدة):
| table | type | key | rows |
|---|---|---|---|
| orders | ALL | NULL | 5 |
EXPLAIN SELECT * FROM ordersWHERE order_date >= '2025-01-01' AND order_date < '2026-01-01';الناتج (أهم الأعمدة):
| table | type | key | rows |
|---|---|---|---|
| orders | range | idx_orders_date | 1 |
الاستعلامان يعطيان النتيجة نفسها، لكن الأول يفحص الجدول كله! السبب: الفهرس يخزن التواريخ، لا ناتج الدالة YEAR. فإذا وضعت العمود داخل دالة، اضطرت MySQL لحساب الدالة لكل صف.
والأمر نفسه مع LIKE:
| الشرط | type | السبب |
|---|---|---|
name LIKE 'كتاب%' |
range |
بداية النص معروفة، فالترتيب مفيد |
name LIKE '%أقلام' |
ALL |
البداية مجهولة، فلا يفيد الترتيب |
الفهارس ليست مجانية
Section titled “الفهارس ليست مجانية”- الكتابة تبطؤ: كل
INSERTوUPDATEوDELETEيحدّث الجدول وكل فهارسه. - المساحة: كل فهرس نسخة مرتبة من أعمدته على القرص.
| فهرِس | لا تفهرِس |
|---|---|
أعمدة WHERE المتكررة |
الجداول الصغيرة |
أعمدة JOIN و ORDER BY |
أعمدة قليلة التنوع مثل «نعم/لا» |
| الأعمدة التي يجب ألا تتكرر (فريد) | أعمدة لا تبحث بها |
حذف الفهرس
Section titled “حذف الفهرس”DROP INDEX idx_orders_date ON orders;في MySQL يجب ذكر اسم الجدول بعد ON. والحذف لا يمس البيانات: يختفي الطريق المختصر فقط.
جرّب بنفسك
Section titled “جرّب بنفسك”- اعرض فهارس جدول
customersبـSHOW INDEX. - اعرض خطة تنفيذ
SELECT * FROM customers WHERE city = 'القاهرة';، ثم أنشئ فهرساً علىcityوقارن. - اعرض خطة
SELECT * FROM products WHERE id = 3;ولاحظ قيمةtype. - تحدٍّ: اكتب استعلامين يجلبان منتجات أُضيفت في شهر أكتوبر 2026: واحداً بـ
MONTH(added_on)وآخر بمدى تواريخ، وأنشئ فهرساً علىadded_onوقارن الخطتين.
أخطاء شائعة
Section titled “أخطاء شائعة”- فهرسة كل الأعمدة: تبطئ الكتابة بلا فائدة. فهرِس ما تبحث به فعلاً.
- دالة على العمود المفهرس:
WHERE YEAR(col) = ...وWHERE col + 1 = ...تعطّلان الفهرس. - ترتيب خاطئ في الفهرس المركّب: العمود الذي تبحث به دائماً يجب أن يكون الأول.
- عدم استخدام EXPLAIN: لا تفترض أن الفهرس مستخدَم. تحقق.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا تعني القيمة ALL في عمود type من ناتج EXPLAIN؟
ALL تعني أن MySQL ستقرأ الجدول صفاً صفاً، وهي علامة على حاجة الاستعلام لفهرس.
سؤال
أي شرط يستفيد من فهرس على العمود order_date؟
المقارنة المباشرة بالعمود تستخدم الفهرس، أما وضع العمود داخل دالة فيعطّله.
الخلاصة
Section titled “الخلاصة”- الفهرس قائمة مرتبة تسمح بالقفز للصفوف المطلوبة بدل فحص الجدول كله.
- المفتاح الأساسي والمفاتيح الأجنبية مفهرسة تلقائياً في InnoDB.
EXPLAINتعرض الخطة: راقبtypeوkeyوrows.- الفهرس المركّب يُستخدم من اليسار، والدوال على العمود تعطّل الفهرس.
- الفهارس تسرّع القراءة وتبطئ الكتابة، فأضفها عند الحاجة وتحقق منها.