Skip to content
العودة إلى المدونة
engineering5 دقيقة قراءة

استراتيجيات الفهرسة في Postgres لاستعلامات أسرع

استراتيجيات فهرسة عملية في Postgres تحوّل الاستعلامات البطيئة إلى سريعة: B-tree والمركّبة وGIN والجزئية والتعبيرات وقراءة خطة الاستعلام.

SummationWorks
استراتيجيات الفهرسة في Postgres لاستعلامات أسرع

الاستعلام الذي يعود في 8 ميلي ثانية على جهاز المطوّر قد يستغرق 8 ثوانٍ في بيئة الإنتاج بمجرّد أن يحتوي الجدول على بضعة ملايين من الصفوف. لم يتغيّر شيء في الكود. ما تغيّر هو أن Postgres نفدت منه الاختصارات فبدأ يقرأ الجدول بأكمله للإجابة عن سؤال كان يُفترض أن يستغرق عملية بحث واحدة. هذه الفجوة بين العرض التجريبي والمنتج الحيّ تكون في الغالب مشكلة فهرسة (indexing).

الفهرسة هي أعلى أعمال التحسين مردوداً على قاعدة البيانات، وهي أيضاً الأسهل في ارتكاب خطأ دقيق فيها. الفهرس الصحيح يحوّل المسح الكامل للجدول إلى بحث فوري. والفهرس الخاطئ يُبطئ كل عملية كتابة، ويضخّم فاتورة التخزين، ويبقى غير مستخدَم بينما يواصل الاستعلام البطيء زحفه. هذا الدليل يستعرض استراتيجيات الفهرسة التي تُحدث فرقاً فعلياً في أداء استعلامات Postgres.

ماذا يفعل الفهرس فعلاً

الفهرس بنية بيانات منفصلة ومرتّبة تتيح لـ Postgres العثور على الصفوف دون مسح كل صف على حدة. تخيّله كفهرس في نهاية كتاب: بدلاً من قراءة 400 صفحة للعثور على كل ذكر لكلمة «الفواتير»، تقفز مباشرة إلى الصفحات المُدرجة.

النوع الافتراضي والأهم من الفهارس في Postgres هو B-tree. وهو يتعامل مع المساواة (=) والنطاقات (<، >، BETWEEN) والترتيب (ORDER BY) ومطابقة بدايات النصوص. وبالنسبة للغالبية العظمى من استعلامات الأعمال، يكون B-tree هو الخيار الصحيح.

المقايضة حقيقية وتستحق أن تُذكر بوضوح:

  • القراءة تصبح أسرع. عمليات البحث والربط (joins) والترتيب على الأعمدة المفهرسة يمكنها تجاوز الجدول كاملاً.
  • الكتابة تصبح أبطأ. كل عملية INSERT وUPDATE وDELETE يجب أن تحدّث أيضاً كل فهرس متأثّر.
  • التخزين يكبر. الفهارس بيانات حقيقية على القرص وتحتاج إلى صيانة.

لهذا تُفهرس بقصد لا بشكل تلقائي. الفهرس الذي لا يخدم أي استعلام هو تكلفة خالصة.

افهرس الأعمدة التي تُرشّح عليها فعلاً

الخطأ الأكثر شيوعاً هو فهرسة الأعمدة الخاطئة، أو افتراض أن فهرس المفتاح الأساسي (primary key) يغطّي كل شيء. يقوم Postgres تلقائياً بفهرسة المفاتيح الأساسية وقيود التفرّد، لكنه لا يفهرس المفاتيح الخارجية (foreign keys) ولا الأعمدة الموجودة في جُمل WHERE.

ابدأ من الاستعلامات المهمة، لا من بنية الجداول. لكل استعلام بطيء، انظر إلى ثلاثة مواضع:

  • جملة WHERE. الأعمدة المستخدمة لترشيح الصفوف هي أوّل المرشّحين للفهرسة.
  • شروط JOIN. أعمدة المفاتيح الخارجية المستخدمة في الربط تستحق فهرساً دائماً تقريباً؛ فبدونه يفرض الربط على الجداول الكبيرة مسحاً كاملاً.
  • ORDER BY وGROUP BY. يمكن للفهرس أن يجعل Postgres يُعيد الصفوف مرتّبة مسبقاً، فيتجنّب عملية ترتيب مكلفة في الذاكرة.

قاعدة عملية لأداء SQL: افهرس أولاً الأعمدة التي تظهر في استعلاماتك الأكثر تكراراً والأكثر تكلفة. حفنة من الفهارس المختارة بعناية أفضل من عشرات الفهارس التخمينية.

الفهارس المركّبة وترتيب الأعمدة

عندما يُرشّح استعلام على أكثر من عمود، فإن فهرساً مركّباً (composite index) واحداً يغطّي تلك الأعمدة يتفوّق عادةً على عدّة فهارس أحادية العمود منفصلة. لكن ترتيب الأعمدة داخل الفهرس المركّب ليس اعتباطياً، والخطأ فيه هو سبب كثير من قصص «أضفت فهرساً ولم يتسارع شيء».

فهرس مركّب على (status, created_at) يمكنه خدمة:

  • الاستعلامات التي تُرشّح على status وحده.
  • الاستعلامات التي تُرشّح على status و created_at.

لكنه لا يستطيع خدمة استعلام يُرشّح على created_at وحده بكفاءة، لأن ذلك العمود يقع ثانياً في الفهرس. هذه هي قاعدة البادئة اليسرى (leftmost-prefix): الفهرس قابل للاستخدام من اليسار فقط.

التوجيه العملي للترتيب:

  • ضع مرشّحات المساواة (status = 'active') قبل مرشّحات النطاق (created_at > '2026-01-01').
  • ضع العمود الأكثر انتقائية، أي الذي يستبعد أكبر عدد من الصفوف، في موضع أبكر عند تساوي العوامل الأخرى.
  • طابِق الترتيب مع أنماط استعلاماتك الفعلية لا مع ترتيب الأعمدة في الجدول.

ما وراء B-tree: الفهارس المتخصّصة

يتعامل B-tree مع معظم الحالات، لكن Postgres يأتي بأنواع فهارس عدّة مبنية لأحمال عمل محدّدة. واستخدام النوع المناسب قد يحوّل استعلاماً بطيئاً إلى سريع.

GIN لِـ JSONB والمصفوفات والبحث النصّي الكامل

إذا كنت تخزّن البيانات في JSONB، أو تبحث في المصفوفات، أو تُشغّل بحثاً نصّياً كاملاً، فإن فهرس GIN مبنيّ لهذا الغرض تحديداً. فهرس GIN على عمود JSONB يتيح لـ Postgres الإجابة عن «أي الصفوف تحتوي على هذا المفتاح أو القيمة» دون مسح كل مستند. ومعظم التطبيقات التي تتبنّى JSONB ثم تشكو من أداء الاستعلامات ببساطة لم تُضف فهرس GIN أبداً.

الفهارس الجزئية للمجموعات النشطة

الفهرس الجزئي (partial index) يغطّي فقط الصفوف المطابقة لشرط معيّن. إذا كانت 95% من صفوفك مؤرشفة وتطبيقك يستعلم دائماً تقريباً عن النشطة، فإن فهرساً جزئياً WHERE status = 'active' يكون أصغر وأسرع وأرخص في الصيانة من فهرس كامل يغطّي الجدول بأكمله.

فهارس التعبيرات للبحث المحسوب

عندما تستعلم عن قيمة محوّلة مثل WHERE lower(email) = ...، فإن الفهرس العادي على email لن يُستخدم. فهرس التعبير (expression index) على lower(email) يطابق الاستعلام ويستعيد المسار السريع. وتسجيل الدخول غير الحسّاس لحالة الأحرف مثال نموذجي على ذلك.

اقرأ خطة الاستعلام ثم قرّر

لا تخمّن أبداً ما إذا كان الفهرس يفيد. يخبرك Postgres بالضبط بما يفعله عبر EXPLAIN ANALYZE، الذي يُشغّل الاستعلام ويُبلّغ عن الخطة الفعلية والأزمنة.

عند قراءة الخطة، انتبه إلى:

  • Seq Scan على جدول كبير حيث توقّعت عملية بحث. هذا يعني أن Postgres يقرأ كل صف، وغالباً بسبب فهرس مفقود أو غير مستخدَم.
  • Index Scan أو Index Only Scan. الفهرس قيد الاستخدام. وعملية Index Only Scan، حيث يُجيب Postgres بالكامل من الفهرس دون لمس الجدول، هي أسرع نتيجة وتستحق التصميم لها عبر الفهارس المغطّية (covering indexes).
  • الصفوف المقدّرة مقابل الفعلية. الفارق الكبير يعني أن إحصاءات المخطّط قديمة؛ وتشغيل ANALYZE يُحدّثها وكثيراً ما يصلح خطة سيئة من تلقاء نفسه.

وهناك عادات إضافية تُبقي استراتيجية الفهرسة سليمة عبر الزمن:

  • احذف الفهارس التي لا يستخدمها أي استعلام. يتتبّع Postgres الاستخدام في pg_stat_user_indexes، والفهارس غير المستخدمة عبء كتابة خالص.
  • ابنِ الفهارس على جداول الإنتاج المزدحمة باستخدام CREATE INDEX CONCURRENTLY كي لا تُقفل عمليات الكتابة أثناء الإنشاء.
  • أعد فحص خططك بعد نمو البيانات الكبير؛ فالفهرس الذي كان غير ضروري عند 10,000 صف قد يصبح أساسياً عند 10 ملايين.

أهم النقاط

  • الفهارس تقايض كتابة أبطأ وتخزيناً أكبر مقابل قراءة أسرع بكثير، لذا أضِفها بقصد بناءً على استعلامات فعلية لا بشكل افتراضي.
  • افهرس الأعمدة في جُمل WHERE وJOIN وORDER BY؛ فالمفاتيح الخارجية لا تُفهرس تلقائياً وكثيراً ما تسبّب ربطاً بطيئاً.
  • في الفهارس المركّبة، ابدأ بمرشّحات المساواة وبالأعمدة الأكثر انتقائية، واحترم قاعدة البادئة اليسرى.
  • لجأ إلى فهارس GIN والجزئية والتعبيرات عندما لا يناسب B-tree حالات JSONB أو المجموعات النشطة أو البحث المحسوب.
  • استخدم EXPLAIN ANALYZE للتأكّد من استخدام الفهرس، واحذف الفهارس غير المستخدمة لإبقاء الكتابة سريعة.

نادراً ما يُصلح أداء قاعدة البيانات نفسه بنفسه، والاستعلام البطيء تحت الضغط قد يحدّ بصمت من مدى نمو منتجك. في SummationWorks نصمّم قواعد بيانات Postgres ونُدقّقها ونضبط أداءها لمنصّات الويب والتجارة الإلكترونية وأنظمة POS في الخليج ومصر وما بعدهما. تعرّف على خدماتنا، واطّلع على أعمالنا، أو تواصل معنا لتكتشف أين تترك قاعدة بياناتك سرعةً على الطاولة.

عن الكاتب

SummationWorks

SummationWorks is a software development company building web apps, mobile apps, and AI tools for startups and growing businesses across the US, UK, and GCC.

المزيد عنّا

لديك مشروع في ذهنك؟

لنحوّل فكرتك إلى برمجيات جاهزة للإنتاج.

ابدأ مشروعًا