ترتيب أعمدة الـ Composite Index في PostgreSQL
لو عندك فهرس مركّب على (created_at, status) واستعلامك بيفلتر بـ status، الفهرس غالبًا مش بيتستخدم زي ما انت متخيّل. عكس ترتيب العمودين بس ينقّل نفس الاستعلام من 420ms لـ 2.8ms — من غير ما تغيّر سطر في الكود ولا تكبّر السيرفر.
هذا المقال يتطلب مستوى محترف. الافتراض إن عندك خبرة بـ SQL وEXPLAIN، وبتشتغل على جدول كبير (ملايين الصفوف) على PostgreSQL.
المشكلة باختصار
أغلب الـ ORMs بتسمحلك تعمل فهرس على أكتر من عمود بسطر واحد. الفريق بيفتكر إن أي استعلام بيلمس الأعمدة دي هيستفيد من الفهرس. ده غلط. الفهرس المركّب مش "مجموعة فهارس على كل عمود" — هو ترتيب واحد على تسلسل الأعمدة بالترتيب اللي كتبته بالظبط. لو الترتيب غلط، الـ planner بيتجاهل الفهرس ويرجع لـ Seq Scan على ملايين الصفوف.
المثال أولًا: دليل التليفونات
تخيّل دليل تليفونات ورقي مرتّب باسم العائلة الأول، وبعدين الاسم الشخصي. لو بتدوّر على "علي محمد"، بتلاقيه في ثواني: تفتح على حرف "ع" بتاع العائلة، وبعدين بتلاقي "علي" جوه صفحات العائلة. لكن لو طلبت منك "هاتلي كل الناس اللي اسمهم الشخصي محمد بغض النظر عن العائلة"، الدليل مايساعدكش خالص. هتضطر تقلب كل صفحة من الأول للآخر. مش لإن الأسماء مش موجودة، لكن لإن الترتيب مبني على العائلة الأول، والاسم الشخصي مبعتر جوه كل عائلة.
الفهرس المركّب بيشتغل بنفس المنطق بالظبط. الترتيب على العمود الأول هو اللي بيحكم، والعمود التاني مرتّب جوه قيم العمود الأول بس.
الشرح الدقيق: قاعدة الـ Leftmost Prefix
الفهرس المركّب في PostgreSQL هو B-tree مبني على tuple مرتّب: (col_a, col_b, col_c). الترتيب معجمي — يرتّب بـ col_a أولًا، وعند تساوي col_a يرتّب بـ col_b، وهكذا. النتيجة العملية إن الفهرس بيخدم بكفاءة الأنماط دي بس:
WHERE col_a = ?WHERE col_a = ? AND col_b = ?WHERE col_a = ? AND col_b = ? AND col_c > ?
لكنه لا يخدم بكفاءة WHERE col_b = ? لوحده، لأن col_b مبعتر داخل قيم col_a — زي الاسم الشخصي في الدليل. ده اسمه قاعدة الـ leftmost prefix: الفهرس ينفع طالما استعلامك بيستخدم بادئة متصلة من الأعمدة من الشمال.
القاعدة العملية اللي بتحسم الترتيب اسمها Equality, Sort, Range (ESR): حُط أعمدة المساواة (=) الأول، بعدين عمود الترتيب (ORDER BY)، وآخر حاجة عمود المدى (>, <, BETWEEN). سبب الترتيب ده إن عمود المدى بيكسر إمكانية استغلال أي عمود بعده في الفهرس.
مثال تنفيذي بالأرقام
عندنا جدول orders فيه 12 مليون صف. حوالي 2% منهم حالتهم pending. الاستعلام المتكرر: