يتطلب مستوى: مبتدئ
الـ Index في قواعد البيانات: ليه استعلامك بطيء وإزاي تسرّعه بسطر واحد
لو استعلام بسيط زي البحث عن مستخدم بالإيميل بياخد أكثر من نص ثانية على جدول فيه مليون صف، المشكلة مش في السيرفر ولا في لغة البرمجة. المشكلة إن قاعدة البيانات بتقرا كل صف واحد واحد. الحل سطر SQL واحد اسمه Index، وبيوصّل نفس الاستعلام من 480 مللي ثانية لأقل من 1 مللي ثانية.
المشكلة باختصار
عندك جدول users فيه مليون صف. بتكتب استعلام يدوّر على إيميل معيّن. بدون index، القاعدة بتعمل حاجة اسمها Sequential Scan، يعني بتفتح كل صف من الأول للآخر وتقارن الإيميل لحد ما تلاقيه. لو الصف اللي انت عايزه في الآخر، دفعت تمن قراءة المليون صف كلهم. المشكلة دي بتفضل مخبّية وانت شغّال على بيانات قليلة، وبتظهر فجأة أول ما تكبر: نفس الاستعلام كان سريع على 1000 صف، وبقى بطيء على مليون.
يعني إيه Index؟ خلّيني أبسّطها الأول
تخيّل كتاب 900 صفحة وعايز تلاقي فيه كلمة معيّنة. لو مفيش فهرس في آخر الكتاب، هتقلّب صفحة صفحة لحد ما توصل، ممكن بعد وقت طويل. الفهرس بيقولك على طول: الكلمة دي موجودة في صفحة 12 و207 و688. فتفتح الصفحات دي دايركت من غير ما تقلّب الكتاب كله.
الـ Index في قاعدة البيانات هو نفس الفكرة بالظبط. هو نسخة مرتّبة من العمود اللي بتدوّر بيه، ومعاها مؤشّر لمكان الصف الأصلي على الديسك. علميًا، أغلب قواعد البيانات بتخزّن الـ index في هيكل اسمه B-tree، وهي شجرة متوازنة بتنزل من الجذر لفرع لورقة في 3 أو 4 قفزات بس. يعني بدل ما تقرا مليون صف، بتقرا حوالي 3 صفحات فهرس وتوصل للصف. ده الفرق بين مقارنة تتعمل مليون مرة ومقارنة تتعمل تلات مرات.
الحل: أضف أول index وقيس بنفسك
قبل ما تضيف أي حاجة، اتفرّج على اللي بيحصل فعلاً باستخدام EXPLAIN ANALYZE. الأمر ده بيوريك الخطة اللي القاعدة هتمشي عليها والزمن الحقيقي للاستعلام، من غير تخمين.
-- قبل: القاعدة بتعمل Seq Scan على مليون صف
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'ali@example.com';
-- Seq Scan on users ... actual time=480.312 ms
-- أضف الفهرس (سطر واحد)
CREATE INDEX idx_users_email ON users (email);
-- بعد: نفس الاستعلام بقى Index Scan
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'ali@example.com';
-- Index Scan using idx_users_email ... actual time=0.912 ms
الأرقام دي مقاسة على PostgreSQL 16 بجدول فيه مليون صف: من 480 مللي ثانية لـ 0.9 مللي ثانية، يعني أسرع حوالي 533 مرة. ركّز إن ده مش تقدير، ده رقم مكتوب في مخرجات EXPLAIN ANALYZE قدامك في السطر actual time.
الـ trade-off: مفيش حاجة ببلاش
الـ index مش سحر، وله تمن لازم تعرفه بدل ما تفاجأ بيه بعدين. أول تكلفة: الـ index بياخد مساحة على الديسك، غالبًا بين 10% و30% من حجم الجدول لكل index. تاني تكلفة وأهم: كل INSERT وUPDATE وDELETE على العمود المفهرس بيخلّي القاعدة تحدّث شجرة الـ B-tree كمان، مش الجدول بس.
يعني الـ trade-off هنا واضح: بتكسب سرعة كبيرة في القراءة، وبتخسر شوية سرعة في الكتابة ومساحة على الديسك. القاعدة العملية: لو الجدول بيتقري أكتر بكتير مما بيتكتب (وده حال أغلب الجداول في التطبيقات)، الـ index مكسب صافي. الافتراض هنا إنك بتفهرس عمود بتدوّر بيه فعلًا في WHERE أو JOIN أو ORDER BY، مش أي عمود على الفاضي.
متى لا تستخدم الـ Index
مش كل عمود يستاهل index. متعملش index في الحالات دي:
- الجدول صغير (بضع آلاف صف)؛ القاعدة هتعمل Seq Scan أسرع من إنها تفتح الشجرة أصلًا.
- العمود قيمه متكررة جدًا، زي عمود فيه قيمتين بس (نشط/غير نشط)؛ الـ index هنا مش بيفرّق كتير وساعات القاعدة بتتجاهله.
- الجدول بيتكتب فيه بكثافة وبيتقري نادر؛ هتدفع تكلفة تحديث الـ index في كل كتابة بدون مكسب حقيقي في القراءة.
وبلاش تعمل index على كل عمود "احتياطي على ما تحتاجه". كل index زيادة بيبطّئ الكتابة ويستهلك مساحة، من غير ما حد يستخدمه في استعلام.
الخطوة التالية
افتح أبطأ استعلام عندك دلوقتي وحطّ قبله EXPLAIN ANALYZE. لو لقيت كلمة Seq Scan على جدول كبير مع شرط WHERE على عمود معيّن، اعمل index على العمود ده بـ CREATE INDEX وأعد القياس. لو الزمن نزل، انت لقيت المشكلة وحليتها. لو ماتغيّرش، فالقاعدة غالبًا شايفة إن الـ index مش مفيد هنا، وساعتها راجع نوع الشرط اللي بتستخدمه.
مصادر
- PostgreSQL Documentation — Indexes: postgresql.org/docs/current/indexes.html
- PostgreSQL Documentation — Using EXPLAIN: postgresql.org/docs/current/using-explain.html
- PostgreSQL Documentation — B-Tree Index Type: postgresql.org/docs/current/btree.html
- Use The Index, Luke! — دليل عملي لفهم فهارس B-tree: use-the-index-luke.com