أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Database Index بالعربي: ليه استعلام بياخد 8 ثواني والثاني 12ms

📅 ٢٦ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
Database Index بالعربي: ليه استعلام بياخد 8 ثواني والثاني 12ms

المستوى: مبتدئ

لو شغّلت SELECT id, total FROM orders WHERE customer_email = 'x@y.com' على جدول فيه 2 مليون صف ولقيته بياخد 8 ثواني، المشكلة مش في السيرفر ولا في PostgreSQL. المشكلة إن قاعدة البيانات بتقرا الجدول كله سطر سطر علشان تلاقيلك صف واحد. الـ Index بيحل ده، وفي الغالب بيوصل بالاستعلام من 8 ثواني لـ 12ms من غير ما تغيّر سطر واحد في كود التطبيق.

Database Index بالعربي: من 8 ثواني لـ 12ms بدون لمس الكود

المشكلة باختصار

قواعد البيانات بتخزّن الصفوف في ملفات على القرص بترتيب الإدخال، مش بترتيب البحث. لما تطلب صف بشرط WHERE، الـ query planner عنده خيارين: يقرا الجدول كله (Sequential Scan)، أو يستخدم Index لو موجود على العمود ده. على جدول صغير، Sequential Scan أسرع لأن قراءة كل البلوكات في مرة واحدة أوفر من قفز عشوائي. على جدول أكبر من 50 ألف صف، الفرق بيبدأ يبان: ثواني بدل ميلي ثواني، ومع نمو البيانات الفجوة بتكبر بشكل مش خطي.

صفوف من خوادم قاعدة بيانات داخل مركز بيانات تمثل أهمية الفهرسة لتسريع الاستعلامات

الـ Index بمثال بسيط

تخيل عندك كتاب طبخ بـ 600 صفحة، وعايز توصفة "الكشري". في غياب فهرس، هتقلّب الكتاب صفحة صفحة من الأول لحد ما تلاقيها — ممكن تاخد دقيقتين. مع وجود فهرس في آخر الكتاب مرتب أبجديًا، بتفتح حرف "ك"، تلاقي "كشري — صفحة 247"، تفتحها مباشرة، انتهيت في 5 ثواني.

الـ Index في قاعدة البيانات بنفس المنطق: هيكل بيانات منفصل بيخزّن (القيمة → موقع الصف على القرص)، مرتب بطريقة بتسمح بالبحث بسرعة لوغاريتمية بدل خطية. الكتاب بيكبر، الفهرس بيكبر بشكل أبطأ بكتير، فالبحث بيفضل سريع.

التعريف العلمي

الـ Index الافتراضي في PostgreSQL وMySQL هو B-tree Index — اختصار Balanced Tree. ده شجرة متعددة الفروع متوازنة، بتقسّم نطاق القيم على عدة مستويات. لجدول فيه مليون صف، عمق الشجرة بيكون 3 إلى 4 مستويات بس. يعني بدل ما تقرا مليون صف، بتقرا 4 صفحات فهرس + صفحة بيانات = ~5 قراءات قرص بدل مليون.

الفرضية المهمة هنا: الـ B-tree بيشتغل لما تكون بتدوّر بمساواة (=) أو نطاق (BETWEEN, <, >) أو بداية نص (LIKE 'ahmed%'). لكنه ميشتغلش لو بتدوّر بنهاية نص (LIKE '%example.com') — ده محتاج Index من نوع تاني زي GIN مع pg_trgm.

مثال تنفيذي مع EXPLAIN ANALYZE

افترض جدول orders فيه 2 مليون صف، وعمود customer_email بدون Index. خد القياس قبل أي تعديل:

SQL
-- 1) قياس قبل الـ Index
EXPLAIN ANALYZE
SELECT id, total
FROM orders
WHERE customer_email = 'ahmed@example.com';

-- مخرج تقريبي:
-- Seq Scan on orders  (cost=0.00..58420.00 rows=1 width=24)
--   Filter: (customer_email = 'ahmed@example.com')
--   Rows Removed by Filter: 1999999
-- Execution Time: 7843.214 ms

دلوقتي اعمل Index على نفس العمود. في الإنتاج، استخدم CONCURRENTLY علشان متقفلش الجدول على كتابات تانية:

SQL
CREATE INDEX CONCURRENTLY idx_orders_customer_email
ON orders (customer_email);

-- نفس الاستعلام بالظبط
EXPLAIN ANALYZE
SELECT id, total
FROM orders
WHERE customer_email = 'ahmed@example.com';

-- مخرج تقريبي:
-- Index Scan using idx_orders_customer_email on orders
--   Index Cond: (customer_email = 'ahmed@example.com')
-- Execution Time: 11.4 ms

الفرق بين 7843ms و11.4ms ده ~688× أسرع، على نفس الجدول، نفس الكود، نفس الداتا. غيّرت سطر واحد بس. الكلمة CONCURRENTLY مهمة في الإنتاج: بتسمح بإنشاء الـ Index بدون ACCESS EXCLUSIVE lock، يعني التطبيق هيشتغل عادي طول مدة الإنشاء.

شاشة تعرض رسومًا بيانية لقياس زمن استجابة استعلامات قاعدة البيانات قبل وبعد إضافة Index

Composite Index: لما عندك أكتر من شرط

لو الـ WHERE بياخد عمودين (customer_email = ? AND status = 'paid')، Index على عمود واحد لوحده مش هيغطي الحالة بالكامل. الحل: Composite Index على العمودين بترتيب اللي بيفلتر أكتر الأول:

SQL
CREATE INDEX CONCURRENTLY idx_orders_email_status
ON orders (customer_email, status);

الترتيب مهم. هذا الـ Index هيشتغل للـ queries اللي فيها customer_email لوحده، أو customer_email + status. لكنه مش هيشتغل لو فلترت بـ status لوحده. القاعدة: العمود الأكثر تحديدًا (selectivity) في الأول.

Trade-offs اللي لازم تعرفها

الـ Index مش مجاني. كل INSERT أو UPDATE أو DELETE بيحدّث الجدول وبيحدّث كل Index على الجدول. على جدول فيه 5 Indexes، الكتابة بتاخد ~30-50% وقت زيادة لكل عملية. غير كده، الـ Index بياخد مساحة قرص. على جدول 50GB، Index على عمود VARCHAR(255) ممكن ياخد 4-8GB لوحده.

الافتراض هنا إن نسبة القراءة للكتابة في تطبيقك أعلى من 5:1، اللي بيكون صحيح في معظم تطبيقات الويب التقليدية. لو بتكتب أكتر بكتير من ما بتقرا (مثلاً جدول events للـ telemetry بيستقبل 10 آلاف INSERT في الثانية)، فكر مرتين قبل ما تضيف Index على عمود بيتكتب فيه باستمرار.

متى لا تستخدم Index

  • أعمدة فيها تنوّع منخفض جدًا: عمود is_active BOOLEAN بقيمتين بس. الـ planner هيتجاهل الـ Index ويرجع لـ Sequential Scan لأن الـ Index أبطأ في الحالة دي.
  • جداول صغيرة (< 10 آلاف صف): تكلفة قراءة الـ Index ممكن تطلع أعلى من قراءة الجدول كله من الـ memory. PostgreSQL بيحتفظ بالجداول الصغيرة في الـ shared_buffers على طول.
  • أعمدة بتتحدّث بمعدل عالي: عمود last_seen TIMESTAMP بيتحدّث في كل request. الـ Index هيتعمله rewrite في كل update، والتكلفة بتاكل المكسب.
  • بحث بنهاية النص: WHERE email LIKE '%@gmail.com' مش هيستفيد من B-tree. محتاج pg_trgm Index بدل ما تضيف B-tree غلط.

أخطاء شائعة في الـ Indexing

أول خطأ: إضافة Index على كل عمود في الجدول "تحسبًا". ده بياكل مساحة وبيبطّأ الكتابة بدون فايدة قابلة للقياس. ابدأ بالـ Indexes اللي EXPLAIN ANALYZE أثبت إنها بتحوّل Seq Scan لـ Index Scan فعلًا.

تاني خطأ: نسيان CONCURRENTLY في الإنتاج. CREATE INDEX بدون CONCURRENTLY بياخد ACCESS EXCLUSIVE LOCK على الجدول طول مدة الإنشاء، اللي ممكن يكون دقايق على جدول كبير. التطبيق هيوقف فعليًا.

ثالث خطأ: الاعتماد على Index واحد بدون مراقبة استخدامه. pg_stat_user_indexes بيقولك أي Indexes اتقرأ منها فعلًا. Index ميتقريش منه بعد شهر = Index ممكن تشيله.

الخطوة التالية

افتح أبطأ 5 استعلامات في تطبيقك من pg_stat_statements (أو slow query log في MySQL). شغّل EXPLAIN ANALYZE على كل واحد. أي استعلام بيرجع Seq Scan على جدول > 50 ألف صف، اعمل Index على عمود الـ WHERE الأساسي بـ CONCURRENTLY واقيس قبل وبعد. لو الفرق أقل من 10×، الـ Index غالبًا مش حلّ المشكلة، وفيه حاجة تانية: JOIN غلط، ORDER BY على عمود غير مفهرس، أو إن الـ planner بيختار الخطة الغلط بسبب statistics قديمة (شغّل ANALYZE).

المصادر

  • PostgreSQL Documentation — Indexes: postgresql.org/docs/current/indexes
  • PostgreSQL — CREATE INDEX CONCURRENTLY: postgresql.org/docs/current/sql-createindex
  • PostgreSQL — Monitoring Stats & pg_stat_user_indexes: postgresql.org/docs/current/monitoring-stats
  • MySQL Reference Manual — Optimization and Indexes: dev.mysql.com/doc/refman/8.0/en/optimization-indexes
  • Use The Index, Luke! — Anatomy of an Index: use-the-index-luke.com/sql/anatomy

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة