انتقل إلى محتوى الدرس
الدرس 55
دورة MySQL

تحسين أداء الاستعلامات وأفضل ممارسات الفهرسة في MySQLMySQL Query Optimization and Indexing Best Practices

تعلّم تحسين أداء الاستعلامات وأفضل ممارسات الفهرسة في MySQL بأسلوب MySQL 8 الحديث مع أمثلة استعلامات SQL وتطبيقات تفاعلية.
أقسام الدرس
6
الأمثلة
5
اللغة
العربية
تاريخ النشر
غير محدد
المشاهدات
7
محتوى الدرس
في هذا الدرس 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' (يبقي العمود المفهرس مجرداً على اليسار فيقفز الفهرس للنطاق فوراً).
مخطط تحسين أداء الاستعلامات وأفضل ممارسات الفهرسة في MySQL
SQL
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!

SQL
CREATE INDEX idx_customers_birth_month ON customers ((MONTH(birth_date)));

محاكي تحسين الاستعلامات والفهرس المغطي

تعلّمنا سابقاً أن الفهرس الثانوي يخزن قيمة العمود المفهرس ومعه المفتاح الأساسي PRIMARY KEY، فإذا طلب الاستعلام أعمدة أخرى اضطر المحرك للعودة إلى الفهرس العنقودي (Bookmark Lookup) لكل صف. ولكن ماذا لو أضفنا الأعمدة المطلوبة في جملة SELECT إلى نهاية الفهرس المركب نفسه؟

عندئذٍ نحصل على الفهرس المغطي (Covering Index): حيث يجد محرك InnoDB جميع الأعمدة التي يحتاجها الاستعلام داخل العقدة الورقية للفهرس الثانوي مباشرةً، فيستغني تماماً عن قراءة الجدول الأساسي، ويعلن عن ذلك في مخرجات EXPLAIN بالعبارة الذهبية Extra: Using index.

SQL
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!
SQL
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 صفاً فقط في أقل من ملي ثانية بغض النظر عن عمق الصفحة!
SQL
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
التصفح العميق بـ OFFSETLIMIT 20 OFFSET 100000التصفح بالمؤشر WHERE id < last_id LIMIT 20يفحص 20 صفاً فقط بدلاً من 100,020 صف
خلاصة المعرفة ومحطة الانتقال

ملخص الدرس

حصاد الدرس

مراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي

ماذا تعلّمت في هذا الدرس؟

أتقنت في هذا الدرس كتابة الشروط الصديقة للفهرس (Sargable Predicates)، وإنشاء الفهارس الدالية على التعبيرات في MySQL 8، وتصميم الفهارس المغطية للاستعلام (Covering Indexes)، وتجنب فخ النجمة البادئة والتحويل الضمني للأنواع، وتطبيق التصفح السريع بالمؤشر (Keyset Pagination).

الخطوة التالية والدرس القادم

في الوحدة القادمة ننتقل إلى الكائنات المخزنة (Stored Objects) لنبدأ ببناء الإجراءات المخزنة وتمرير المعاملات والتحكم في مسار التنفيذ داخل خادم MySQL.

مشاركة

هل كان هذا الدرس مفيدًا؟

جارٍ تحميل الآراء…

ابنِ لغتك البرمجية

المصطلحات4

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 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

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 2 مفهومةأول بطاقة، أول خطوة
01 SARG SARG
Search Argument Able Predicate
شرط التصفية الصديق للفهرس الذي يبقي العمود المفهرس مجرداً خارج الدوال والمعادلات.
للمراجعة
02 SEEK SEEK
Keyset Cursor Seek Pagination
نمط تقسيم الصفحات السريع بالاعتماد على آخر مفتاح مقروء بدلاً من الإزاحة البطيئة OFFSET.
للمراجعة

اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.

من الفهم للتطبيق

مهامك قبل الاختبار

خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.

0/2مهام منجزة
  1. افتح عميل MySQL 8.4 المحلي ونفّذ استعلام SQL المشروح في قسم كتابة شروط صديقة للفهرس للتحقق من النتائج.

  2. بدّل بين السيناريوهات الثلاثة في المحاكي التفاعلي ضمن قسم الفهارس الدالية على التعبيرات وقارن مخرجات MySQL 8.4.

تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.

مساحة التحدي

اختبر فهمك.
ووصلها للإتقان.

كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.

4أسئلة 1أنواع تحديات 100%هدف الإتقان
تصحيح تلقائي. فرص بلا حدود.

أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.

الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.

لمحة عن التحدي4 أسئلة تنتظرك
01

لماذا يمنع الشرط WHERE YEAR(created_at) = 2026 محرك MySQL من استخدام فهرس B-Tree القياسي المعرّف على العمود created_at؟

اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.

02

كيف تنشئ فهرساً دالياً (Functional Index) على التعبير MONTH(birth_date) في MySQL 8.4؟

اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.

03

ماذا يحدث لأداء الاستعلام عندما تفلتر عموداً نصياً VARCHAR باسم phone باستخدام رقم بدون علامتي تنصيص: WHERE phone = 501234567؟

اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.

04

لماذا يتفوق التصفح بالمؤشر (WHERE id < last_seen_id ORDER BY id DESC LIMIT 20) بشدة على أسلوب LIMIT 20 OFFSET 100000 في الصفحات البعيدة؟

اختر الإجابة الأدق وفق قواعد محرك MySQL 8.4.