في هذا الدرس 6تصفّح المواضيع
من أكثر الأخطاء شيوعاً في قواعد البيانات أن ينشئ المطور فهرساً صحيحاً على عمود التاريخ created_at أو السعر unit_price، ثم يكتب شرط WHERE يغلف العمود داخل دالة مثل WHERE YEAR(created_at) = 2026 أو معادلة حسابية مثل WHERE unit_price + 50 > 200! عندئذٍ يفقد محرك MySQL القدرة على استخدام شجرة B+Tree لأن الفهرس يخزن قيم created_at الخام وليس ناتج دالة YEAR().
يُسمى الشرط الذي يسمح للمحرك بالبحث المباشر في شجرة الفهرس شرطاً صديقاً للفهرس (Sargable Predicate — اختصاراً لـ Search Argument Able):
- غير صديق للفهرس (Non-Sargable):
WHERE YEAR(created_at) = 2026(يجبر المحرك على فحص كل صف وحساب الدالة). - صديق للفهرس (Sargable):
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'(يبقي العمود المفهرس مجرداً على اليسار فيقفز الفهرس للنطاق فوراً).
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
ماذا لو كان منطق التطبيق يفرض عليك البحث بدالة لا يمكن تحويلها إلى نطاق بسيط، مثل البحث عن العملاء الذين يصادف عيد ميلادهم شهر مايو MONTH(birth_date) = 5 بغض النظر عن سنة الميلاد، أو البحث بحروف صغيرة LOWER(username)؟
بدءاً من الإصدار MySQL 8.0.13، يمكنك إنشاء فهرس دالي على التعبيرات (Functional Index) بوضع التعبير أو الدالة بين قوسين إضافيين ((expression)) داخل أمر CREATE INDEX؛ فيقوم محرك InnoDB بحساب قيمة الدالة مسبقاً وفهرسة النتيجة داخل شجرة B+Tree!
CREATE INDEX idx_customers_birth_month ON customers ((MONTH(birth_date)));
محاكي تحسين الاستعلامات والفهرس المغطي
تعلّمنا سابقاً أن الفهرس الثانوي يخزن قيمة العمود المفهرس ومعه المفتاح الأساسي PRIMARY KEY، فإذا طلب الاستعلام أعمدة أخرى اضطر المحرك للعودة إلى الفهرس العنقودي (Bookmark Lookup) لكل صف. ولكن ماذا لو أضفنا الأعمدة المطلوبة في جملة SELECT إلى نهاية الفهرس المركب نفسه؟
عندئذٍ نحصل على الفهرس المغطي (Covering Index): حيث يجد محرك InnoDB جميع الأعمدة التي يحتاجها الاستعلام داخل العقدة الورقية للفهرس الثانوي مباشرةً، فيستغني تماماً عن قراءة الجدول الأساسي، ويعلن عن ذلك في مخرجات EXPLAIN بالعبارة الذهبية Extra: Using index.
EXPLAIN SELECT order_id, status, total_amount FROM orders WHERE customer_id = 102 AND status = 'PAID';
حتى لو كتبت العمود المفهرس مجرداً خارج الدوال، هناك فخّان صامتان يعطّلان فهارس B+Tree في الأنظمة الإنتاجية:
- الفخ الأول (النجمة البادئة Leading Wildcard
LIKE '%text'): عندما تبدأ عبارة المطابقة برمز النسبة المئوية%، يعجز الفهرس الشجري عن معرفة الحرف الأول الذي يبدأ منه البحث؛ بينما يعمل الفهرس بكفاءة تامة إذا كانت النجمة في النهاية فقطLIKE 'text%'(Prefix Match). - الفخ الثاني (التحويل الضمني للأنواع Implicit Type Conversion): إذا كان عمود
phoneأوnational_idمن النوع النصيVARCHAR، وكتبت في الاستعلام رقماً بدون علامتي تنصيصWHERE phone = 501234567، فإن MySQL يحوّل نص كل صف في الجدول إلى رقم لمقارنته، مما يبطل الفهرس تماماً ويحوله إلى فحص كاملtype = ALL!
EXPLAIN SELECT customer_id, full_name, phone FROM customers WHERE phone LIKE '050123%';
عند تقسيم النتائج إلى صفحات (Pagination)، يعمل الأسلوب التقليدي LIMIT 20 OFFSET 0 بسرعة في الصفحة الأولى، لكن عندما ينتقل المستخدم إلى صفحة بعيدة مثل LIMIT 20 OFFSET 100000، فإن محرك MySQL يضطر لقراءة وفرز 100,020 صفاً كاملةً ثم يرمي أول 100,000 صف في سلة المهملات ليرجع لك 20 صفاً فقط!
الحل الهندسي المعتمد في المنصات الكبرى هو التصفح بالمؤشر أو المفتاح (Keyset / Seek Pagination):
- بدلاً من تمرير رقم الإزاحة
OFFSET، نحتفظ بآخر قيمة مفتاح أساسي ظهرت في الصفحة الحالية (مثلاًlast_order_id = 400000). - نطلب الصفحة التالية بالشرط
WHERE order_id < 400000 ORDER BY order_id DESC LIMIT 20. - يقفز المحرك عبر شجرة الفهرس العنقودي مباشرةً إلى المعرّف
400000ويقرأ20صفاً فقط في أقل من ملي ثانية بغض النظر عن عمق الصفحة!
SELECT order_id, customer_id, total_amount FROM orders WHERE order_id < 400000 ORDER BY order_id DESC LIMIT 20;
يلخّص الجدول المرجعي التالي قائمة المراجعة الهندسية لتحسين استعلامات وفهارس MySQL 8.4 وتحويل الأنماط البطيئة إلى أنماط عالية الأداء:
| مشكلة الأداء الشائعة | النمط البطيء (Anti-Pattern) | النمط المحسّن في MySQL 8.4 | الأثر على خطة EXPLAIN |
|---|---|---|---|
| دالة حول عمود مفهرس | WHERE YEAR(created_at) = 2026 | نطاق Sargable أو فهرس دالي ((YEAR(created_at))) | يتحول من type = ALL إلى range أو ref |
| جلب كافة الأعمدة بلا حاجة | SELECT * FROM orders WHERE ... | تحديد الأعمدة المطلوبة فقط + فهرس مغطٍّ | تظهر Extra: Using index وتختفي قراءات الجدول |
| النجمة البادئة في النصوص | WHERE title LIKE '%mysql%' | بادئة يمنى LIKE 'mysql%' أو فهرس FULLTEXT | يستبدل الفحص الكامل بالبحث الشجري أو المعكوس |
| التحويل الضمني للأنواع | WHERE varchar_phone = 501234567 | تمرير نص مطابق للنوع WHERE varchar_phone = '0501234567' | يمنع تعطيل الفهرس ويحافظ على type = ref |
التصفح العميق بـ OFFSET | LIMIT 20 OFFSET 100000 | التصفح بالمؤشر WHERE id < last_id LIMIT 20 | يفحص 20 صفاً فقط بدلاً من 100,020 صف |
ملخص الدرس
حصاد الدرسمراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي
ماذا تعلّمت في هذا الدرس؟
أتقنت في هذا الدرس كتابة الشروط الصديقة للفهرس (Sargable Predicates)، وإنشاء الفهارس الدالية على التعبيرات في MySQL 8، وتصميم الفهارس المغطية للاستعلام (Covering Indexes)، وتجنب فخ النجمة البادئة والتحويل الضمني للأنواع، وتطبيق التصفح السريع بالمؤشر (Keyset Pagination).
الخطوة التالية والدرس القادم
في الوحدة القادمة ننتقل إلى الكائنات المخزنة (Stored Objects) لنبدأ ببناء الإجراءات المخزنة وتمرير المعاملات والتحكم في مسار التنفيذ داخل خادم MySQL.
سجّل الدخول لحفظ تقدمك
تابع إنجاز المواضيع والمصطلحات والمهام، وكمّل من آخر خطوة على أي جهاز.
المصطلحات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 Query Optimization تحسين الاستعلامات
- تعلّم تحسين أداء الاستعلامات وأفضل ممارسات الفهرسة في MySQL بأسلوب MySQL 8 الحديث مع أمثلة استعلامات SQL وتطبيقات تفاعلية.
- للمراجعة
- 02 Writing Sargable Filter Conditions كتابة شروط صديقة للفهرس
- المفهوم الهندسي الأساسي وقواعد التنفيذ العملي لقسم كتابة شروط صديقة للفهرس في قواعد بيانات MySQL 8.4 الحديثة.
- للمراجعة
- 03 Functional Indexes in MySQL 8 الفهارس الدالية على التعبيرات
- المفهوم الهندسي الأساسي وقواعد التنفيذ العملي لقسم الفهارس الدالية على التعبيرات في قواعد بيانات MySQL 8.4 الحديثة.
- للمراجعة
- 04 Covering Indexes Using Index Only الفهارس المغطية للاستعلام بالكامل
- المفهوم الهندسي الأساسي وقواعد التنفيذ العملي لقسم الفهارس المغطية للاستعلام بالكامل في قواعد بيانات MySQL 8.4 الحديثة.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
الاختصارات2
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 SARG SARG
- Search Argument Able Predicate
- شرط التصفية الصديق للفهرس الذي يبقي العمود المفهرس مجرداً خارج الدوال والمعادلات.
- للمراجعة
- 02 SEEK SEEK
- Keyset Cursor Seek Pagination
- نمط تقسيم الصفحات السريع بالاعتماد على آخر مفتاح مقروء بدلاً من الإزاحة البطيئة OFFSET.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
مهامك قبل الاختبار
خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.
-
افتح عميل MySQL 8.4 المحلي ونفّذ استعلام SQL المشروح في قسم كتابة شروط صديقة للفهرس للتحقق من النتائج.
-
بدّل بين السيناريوهات الثلاثة في المحاكي التفاعلي ضمن قسم الفهارس الدالية على التعبيرات وقارن مخرجات MySQL 8.4.
تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.
اختبر فهمك.
ووصلها للإتقان.
كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.
أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.
الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.
لمحة عن التحدي4 أسئلة تنتظرك +
لماذا يمنع الشرط WHERE YEAR(created_at) = 2026 محرك MySQL من استخدام فهرس B-Tree القياسي المعرّف على العمود created_at؟
اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.
كيف تنشئ فهرساً دالياً (Functional Index) على التعبير MONTH(birth_date) في MySQL 8.4؟
اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.
ماذا يحدث لأداء الاستعلام عندما تفلتر عموداً نصياً VARCHAR باسم phone باستخدام رقم بدون علامتي تنصيص: WHERE phone = 501234567؟
اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.
لماذا يتفوق التصفح بالمؤشر (WHERE id < last_seen_id ORDER BY id DESC LIMIT 20) بشدة على أسلوب LIMIT 20 OFFSET 100000 في الصفحات البعيدة؟
اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.