يا جماعة الخير، السلام عليكم ورحمة الله.
بتذكر قبل كم سنة، كنت شغال على نظام لشركة ناشئة، نظام إدارة علاقات عملاء (CRM). في البداية، كان كل إشي تمام التمام، النظام “بلّن” زي ما بحكوها. كان عليه كم مستخدم، كم عميل، والأمور ماشية زي الحلاوة. لكن مع الوقت، الشركة كبرت، وصار عندهم آلاف، وبعدها مئات آلاف العملاء والبيانات تتكدس يوم عن يوم.
وفجأة، بلّشت الشكاوي توصل. “يا أبو عمر، النظام بطيء!”، “يا زلمة، عشان أبحث عن عميل بدي أستنى دقيقة كاملة!”، “التقارير بطلت تطلع!”. بصراحة، الموقف كان محرج جدًا، والضغط كان كبير. قعدت مع الفريق، وبلّشنا رحلة البحث عن “العلّة”. بعد ليلة طويلة من القهوة وتحليل سجلات الخادم (Server Logs)، كانت الصدمة: قاعدة البيانات “بتموت” حرفيًا. كل استعلام بسيط، زي البحث عن عميل بإيميله، كان بيجبر قاعدة البيانات تعمل إشي اسمه “مسح كامل للجدول” (Full Table Scan).
تخيل معي إنك بدك تدور على اسم شخص في دليل هاتف ضخم جداً، بس هالدليل ما إله فهرس أبجدي. شو الحل؟ بدك تمسك الدليل من أول صفحة لآخر صفحة، وتقرأ اسم اسم، لحد ما تلاقي اللي بدك إياه. هذا بالضبط اللي كانت قاعدة البيانات بتعمله مع جدول العملاء اللي وصل ملايين الصفوف. كانت بتبحث في كل صف، واحد واحد. جحيم حقيقي! وقتها عرفت إنه الحل بسيط وموجود، لكننا أغفلناه في زحمة الشغل… الحل كان اسمه “الفهرسة”.
ما هو “المسح الكامل للجداول” (Full Table Scan)؟ وليش هو “الجحيم” بعينه؟
خلونا نبسطها. لما تطلب من قاعدة البيانات معلومة معينة، مثلًا: SELECT * FROM customers WHERE email = 'test@example.com'، قاعدة البيانات لازم تلاقي الصف اللي فيه هذا الإيميل.
إذا ما كان في “فهرس” على حقل الإيميل، قاعدة البيانات ما عندها أي فكرة وين ممكن يكون هالصف. فبتضطر، زي الموظف الكسلان، إنها تمر على كل صف في جدول الـ customers من أوله لآخره، وتقارن قيمة الإيميل في كل صف مع القيمة اللي أنت بتبحث عنها. هذا هو الـ Full Table Scan.
طيب ليش هو سيء لهالدرجة؟
- بطيء جدًا: تخيل جدول فيه 10 ملايين صف. البحث فيه بهالطريقة ممكن ياخذ ثواني أو حتى دقائق، وهذا غير مقبول نهائيًا في أي تطبيق حديث.
- يستهلك موارد الجهاز: هاي العملية بتستهلك كمية كبيرة من قوة المعالج (CPU) وعمليات القراءة من القرص الصلب (I/O)، وهذا بأثر على أداء كل الاستعلامات الثانية اللي بتشتغل بنفس الوقت.
- لا يتوسع (Doesn’t Scale): كل ما زاد حجم الجدول، زاد الوقت بشكل خطي. اليوم دقيقة، بكرة دقيقتين، وبعد سنة بصير ربع ساعة لنفس الاستعلام!
باختصار، المسح الكامل للجداول هو العدو الأول لأداء قواعد البيانات، ولازم نتجنبه قد ما بنقدر.
الفهرس: بطلنا الخارق الذي لا يرتدي عباءة
نرجع لمثال دليل الهاتف. شو اللي بيخليه سهل الاستخدام؟ الفهرس الأبجدي اللي في أوله أو آخره. لو بدك تدور على اسم “محمد”، بتروح مباشرة على حرف “م”، وبتبحث في جزء صغير جداً من الدليل بدل ما تقرأه كله.
هذا بالضبط ما يفعله فهرس قاعدة البيانات (Database Index). الفهرس هو بنية بيانات خاصة (عادة ما تكون شجرة B-Tree) بتخزن قيم عمود معين (أو أعمدة) مع “مؤشر” (pointer) بيشير للمكان الفعلي للصف في الجدول على القرص الصلب.
فلما ننشئ فهرس على عمود الـ email، قاعدة البيانات بتبني “دليل هاتف” خاص بهذا العمود. ولما يجيها استعلام ببحث عن إيميل معين، بدل ما تمسح الجدول كله، بتروح مباشرة على الفهرس، بتبحث فيه بسرعة خيالية (لأنه مرتب)، بتلاقي الإيميل وبتعرف مكانه على الهارد ديسك، وبترجعلك البيانات. العملية بتتحول من دقائق إلى أجزاء من الثانية.
كيف نُنشئ فهرسًا؟ (أمثلة عملية)
إنشاء الفهرس سهل جدًا. لنفترض أن لدينا جدول المستخدمين التالي:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100) NOT NULL,
country_code VARCHAR(2),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
والآن، نريد أن نبحث بشكل متكرر عن مستخدم عبر بريده الإلكتروني:
SELECT * FROM users WHERE email = 'some.user@domain.com';
بدون فهرس، هذا الاستعلام سيؤدي إلى مسح كامل للجدول. لإنشاء فهرس على عمود email، نستخدم الأمر التالي:
CREATE INDEX idx_users_email ON users (email);
هيك إحنا حكينا لقاعدة البيانات: “لو سمحتي، جهزيلي فهرس لعمود الإيميل عشان أبحث فيه بسرعة”. اسم الفهرس idx_users_email هو اسم اختياري، لكن من الممارسات الجيدة تسميته بطريقة تدل على الجدول والعمود اللي بفهرسه.
كيف تتأكد أن قاعدة البيانات تستخدم الفهرس؟
معظم أنظمة قواعد البيانات (مثل MySQL, PostgreSQL) توفر أمرًا سحريًا اسمه EXPLAIN. إذا وضعته قبل جملة الـ SELECT، قاعدة البيانات ما بتنفذ الاستعلام، بل بتشرحلك “خطة التنفيذ” (Execution Plan) اللي راح تتبعها.
قبل الفهرس:
EXPLAIN SELECT * FROM users WHERE email = 'some.user@domain.com';
ستجد في الناتج شيئًا مثل type: ALL أو ما يشير إلى Full Table Scan.
بعد الفهرس:
EXPLAIN SELECT * FROM users WHERE email = 'some.user@domain.com';
ستجد في الناتج شيئًا مثل type: ref أو Index Scan، وهذا يعني أن قاعدة البيانات استخدمت الفهرس بنجاح. يا سلام!
أنواع الفهارس: مش كل الفهارس انخلقت متساوية
الفهرسة عالم، وفيها أنواع مختلفة حسب الحاجة:
1. الفهرس المركب (Composite Index)
أحيانًا، بحثنا بكون على أكثر من عمود. مثلاً، نبحث عن المستخدمين من دولة معينة والذين سجلوا بعد تاريخ معين.
SELECT * FROM users WHERE country_code = 'PS' AND last_name = 'Omar';
هنا، الفهرس على country_code لحاله أو last_name لحاله قد لا يكون كافيًا. الحل هو إنشاء فهرس مركب يجمع العمودين:
CREATE INDEX idx_users_country_lastname ON users (country_code, last_name);
نصيحة مهمة: ترتيب الأعمدة في الفهرس المركب مهم جدًا! ضع العمود الذي تستخدمه أكثر في البحث أو الذي يحتوي على قيم أكثر تنوعًا (higher cardinality) في البداية.
2. الفهرس الفريد (Unique Index)
هذا الفهرس له وظيفتين: تسريع البحث، وضمان عدم تكرار القيمة في العمود. عمود البريد الإلكتروني email أو اسم المستخدم username هما مثالان ممتازان.
CREATE UNIQUE INDEX uidx_users_email ON users (email);
عند وجود هذا الفهرس، لو حاولت إضافة مستخدم جديد بنفس الإيميل الموجود مسبقًا، قاعدة البيانات سترفض العملية وتعطيك خطأ.
نصائح أبو عمر الذهبية (من الكيس)
على مدار سنين الشغل، تعلمت كم شغلة عن الفهرسة بحب أشارككم فيها:
- لا تفرط في الفهرسة: الفهرسة مش ببلاش. كل فهرس بتضيفه هو بمثابة كتاب جديد لازم يتحدث مع كل عملية إضافة (
INSERT)، تعديل (UPDATE)، أو حذف (DELETE) على الجدول. كثرة الفهارس تبطئ عمليات الكتابة وتستهلك مساحة على القرص. “مش كل إشي بنحطله فهرس يا جماعة!”. فهرس فقط ما تحتاجه. - فهرس للأعمدة اللي بنبحث فيها: ركز على فهرسة الأعمدة الموجودة في جمل
WHERE، شروط الربطJOIN، وجمل الترتيبORDER BY. هذه هي الأماكن التي تحقق فيها الفهارس أكبر فائدة. - انتبه لـ “الكاردينالية” (Cardinality): هذا مصطلح تقني معناه “مدى تنوع القيم في العمود”. فهرسة عمود ذي كاردينالية منخفضة جدًا (مثل عمود “الجنس” الذي قد يحتوي فقط على قيمتين أو ثلاث) غالبًا ما تكون عديمة الفائدة. قاعدة البيانات قد تقرر أن المسح الكامل للجدول أسرع من استخدام فهرس غير فعال.
- راجع فهارسك بشكل دوري: مع تطور التطبيق، قد تتغير استعلاماتك. استعلامات قديمة قد لا تعود مستخدمة، وفهارسها أصبحت عبئًا. راجع استخدام الفهارس وحلل استعلاماتك البطيئة بشكل دوري.
الخلاصة: من البحث في كومة قش إلى إيجاد الإبرة بمغناطيس 🧭
العودة لقصتي في البداية، بعد إضافة الفهارس الصحيحة على جدول العملاء والجداول الأخرى، كانت النتيجة مذهلة. الاستعلامات التي كانت تأخذ دقيقة، أصبحت تأخذ 20 ميلي ثانية. النظام رجع “يطير”، العميل صار مبسوط، وأنا وفريقي قدرنا ننام الليل أخيرًا.
فهمك للفهرسة وكيفية استخدامها بذكاء هو ما يميز المبرمج المحترف عن المبرمج العادي. إنها ليست مجرد أداة لتحسين الأداء، بل هي أساس لبناء أنظمة قوية وقابلة للتوسع. لا تهملها أبدًا، واستخدم الأمر EXPLAIN كأنه صديقك المفضل.
أتمنى لكم استعلامات سريعة وأنظمة مستقرة. خليكوا شطار! 😉