26 مايو 2025·5 دقيقة قراءة

تغييرات الفهرس بدون تعطل في PostgreSQL: دليل آمن

تغييرات الفهرس بدون توقف في PostgreSQL باستخدام CONCURRENTLY، فحوصات أقفال بسيطة، وخطوات تراجع واضحة للحفاظ على حركة الإنتاج.

تغييرات الفهرس بدون تعطل في PostgreSQL: دليل آمن

لماذا تسبب تغييرات الفهارس توقفًا (وكيف تتجنبه)

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

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

فكرة "شغّله ليلًا" تفشل لسببين شائعين. العديد من الأنظمة لا تكون هادئة حقًا (مستخدمون عالميون، وظائف دفعات، ETL، نسخ احتياطي). كما أن عمليات الفهرسة يمكن أن تستغرق وقتًا أطول مما تتوقع لأنها تقرأ الكثير من البيانات وتتنافس على المعالج والقرص. إذا انقضت نافذة الصيانة أثناء البناء، ستجد نفسك مضطرًا للاختيار بين الانتظار أو إيقاف العمل.

التغييرات بدون توقف ليست سحرًا. هي في الأساس اختيار العملية الأقل حجبًا، وضع ضوابط (مهلات وفحوصات قرص)، ومراقبة قاعدة البيانات أثناء التشغيل.

يركز هذا الدليل على عادات عملية في الإنتاج:

  • فَضّل بناء الفهارس المتزامن عندما يجب أن تستمر القراءة والكتابة.
  • راقب الأقفال وتقدّم البناء لتتمكن من التفاعل مبكرًا.
  • ضع مسار تراجع إذا تسببت التغييرات في تدهور أو استغرقت وقتًا طويلًا.

ما لا يغطيه: نظرية تصميم الفهارس المتعمقة، تحسين استعلامات على نطاق واسع، أو تغييرات مخطط تعيد كتابة الكثير من البيانات.

نموذج الأقفال البسيط وراء عمل الفهارس

يستخدم PostgreSQL أقفالًا للحفاظ على صحة البيانات عندما تتعامل العديد من الجلسات مع نفس الجدول. القفل هو ببساطة قاعدة تحدد من المسموح له قراءة أو كتابة كائن الآن، ومن يجب أن ينتظر.

غالبًا لا تلاحظ الأقفال لأن PostgreSQL يستخدم أوضاعًا خفيفة تسمح للاستعلامات العادية بالعمل. DDL مختلف. عند إنشاء أو حذف فهرس، يحتاج PostgreSQL إلى قدر كافٍ من التحكم في الجدول للحفاظ على اتساق الكاتالوج والبيانات. كلما زاد هذا التحكم، زادت الجلسات الأخرى التي قد تُجبر على الانتظار.

بناء فهرس مقابل استخدام فهرس

استخدام الفهرس عادةً ما يكون رخيصًا من ناحية الأقفال. يمكن لـ SELECT و UPDATE و DELETE قراءة أو صيانة الفهارس بينما تنفذ جلسات أخرى نفس العمليات.

البناء مختلف. يجب على PostgreSQL فحص الجدول، وفرز أو تجزئة المفاتيح، وكتابة بنية جديدة إلى القرص. هذا العمل يستغرق وقتًا، والوقت هو ما يحوّل "أقفال صغيرة" إلى "م مشاكل كبيرة" في الإنتاج.

ماذا يغيّر CONCURRENTLY (وماذا لا يغيّر)

يأخذ CREATE INDEX العادي قفلًا قويًا يمنع الكتابات طوال مدة البناء. صُمّم CREATE INDEX CONCURRENTLY ليبقي عمليات القراءة والكتابة العادية مستمرة أثناء بناء الفهرس.

لكن "متزامن" لا يعني "خالي من الأقفال". لا تزال هناك نوافذ أقفال قصيرة في البداية والنهاية، ويمكن أن يفشل البناء أو ينتظر إذا كان شيء آخر يحمل أقفالًا غير متوافقة.

النتائج المهمة:

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

اختر النهج الصحيح: متزامن أم لا

أمامك خياران رئيسيان عند تغيير الفهارس: بناء الفهرس بالطريقة العادية (سريع لكنه يسبب حجبًا)، أو بناؤه مع CONCURRENTLY (عادةً لا يسبب حجبًا لحركة التطبيق، لكنه أبطأ وأكثر حساسية للمعاملات الطويلة).

متى يكون CONCURRENTLY الخيار الصحيح

استخدم CREATE INDEX CONCURRENTLY عندما يخدم الجدول حركة فعلية ولا يمكنك إيقاف الكتابات. هو عادةً الخيار الآمن عندما:

  • يكون الجدول كبيرًا لدرجة أن البناء العادي قد يستغرق دقائق أو ساعات.
  • يحتوي الجدول على كتابات مستمرة، ليس مجرد قراءات.
  • لا يمكنك جدولة نافذة صيانة حقيقية.
  • تحتاج لبناء الفهرس أولًا، التحقق منه، ثم حذف فهرس قديم لاحقًا.

متى يكون البناء العادي مقبولًا

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

فكّر في النهج العادي إذا كان البناء سريعًا باستمرار في بيئة التشغيل التجريبي ويمكنك إيقاف الكتابات مؤقتًا.

إذا كنت بحاجة لفرض فريدية، قرّر مبكرًا. يعمل CREATE UNIQUE INDEX CONCURRENTLY لكنه سيفشل إذا كانت هناك قيم مكررة. في كثير من نظم الإنتاج، العثور على المكررات وإصلاحها هو المشروع الحقيقي.

فحوصات قبل اللمس في الإنتاج

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

1) تأكد من أنك لست داخل معاملة. CREATE INDEX CONCURRENTLY سيفشل إذا شغّلته بعد BEGIN، وبعض أدوات الواجهة تضع البيانات داخل معاملة بهدوء. إذا لم تكن متأكدًا، افتح جلسة جديدة وشغّل أمر الفهرسة هناك فقط.

2) عيّن توقعات للوقت والمساحة. عادةً ما تستغرق البنيات المتزامنة وقتًا أطول من البنيات العادية وتحتاج مساحة عمل إضافية أثناء التشغيل. خطّط للفهرس الجديد زائد النفقات المؤقتة، وتأكد من وجود مساحة قرص حرة مريحة.

3) عيّن مهلات زمنية تتوافق مع هدفك. تريد أن يفشل البناء سريعًا إذا لم يستطع الحصول على قفل، لكنك لا تريد أن تموت الجلسة أثناء البناء بسبب statement_timeout متشددة.

4) التقط صورة قياسية قبل التغيير. تريد دليلًا أن التغيير ساعد وطريقة سريعة لاكتشاف التراجعات. سجّل لقطة قبلية: توقيت الاستعلامات البطيئة، EXPLAIN (ANALYZE, BUFFERS) نموذجي، ونظرة سريعة على CPU وIO والاتصالات والمساحة الحرة.

إعدادات جلسة آمنة تستخدمها فرق كثيرة كنقطة بداية (عدّلها حسب قواعدك):

-- Run in the same session that will build the index
SET lock_timeout = '2s';
SET statement_timeout = '0';

خطوة بخطوة: إنشاء فهرس بـ CONCURRENTLY

حافظ على ثبات تطبيقات الجوال
انشئ تطبيقات iOS وAndroid أصلية تعتمد على واجهات برمجة مستقرة أثناء تغيّر قاعدة البيانات.
ابنِ تطبيقًا محمولًا

استخدم CREATE INDEX CONCURRENTLY عندما تحتاج أن تستمر حركة التطبيق ويمكنك تحمّل وقت بناء أطول.

أولًا، قرر بالضبط ما الذي ستبنيه:

  • كن محددًا بشأن ترتيب الأعمدة (يهم).
  • فكّر فيما إذا كان فهرس جزئي يكفي. إذا كانت معظم الاستعلامات تصفي إلى صفوف "نشطة"، قد يكون الفهرس الجزئي أصغر وأسرع وأرخص لصيانته.

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

-- Example: speed up searches by email for active users
CREATE INDEX CONCURRENTLY idx_users_active_email
ON public.users (email)
WHERE status = 'active';

-- Validate it exists
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'users';

-- Check the plan can use it
EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM public.users
WHERE status = 'active' AND email = '[email protected]';

لسجلات التقدّم (مفيدة للتدقيق)، سجّل وقت البدء، ووقت الانتهاء، وأي انتظارات لاحظتها. أثناء التشغيل، يمكنك استعلام pg_stat_progress_create_index من جلسة أخرى.

التحقق ليس فقط "الفهرس موجود." أكد أن المخطط يمكنه اختياره، ثم راقب توقيت الاستعلام الحقيقي بعد النشر. إذا لم يُستخدم الفهرس الجديد، لا تتسرّع في حذف القديم. أصلح الاستعلام أو تعريف الفهرس أولًا.

خطوة بخطوة: استبدال أو إزالة الفهارس دون حجب

أأمن نمط هو الإضافة أولًا، السماح للحركة بالاستفادة من الفهرس الجديد، ثم إزالة القديم فقط بعد ذلك. بهذه الطريقة تملِك نقطة ارتداد عاملة.

استبدال فهرس قديم بواحد جديد (الترتيب الآمن)

  1. أنشئ الفهرس الجديد باستخدام CREATE INDEX CONCURRENTLY.

  2. تحقق أنه يُستخدم. افحص EXPLAIN على الاستعلامات البطيئة التي تهمك، وراقب استخدام الفهرس مع الوقت.

  3. فقط بعد ذلك، احذف الفهرس القديم بشكل متزامن. إن كان الخطر عاليًا، احتفظ بكلا الفهرسين لدورة عمل كاملة قبل إزالة أي منهما.

حذف الفهارس: متى يعمل CONCURRENTLY (ومتى لا)

للفهرس العادي الذي أنشأته بنفسك، عادةً ما يكون DROP INDEX CONCURRENTLY الخيار الصحيح. نقطتان يجب معرفتهما: لا يمكن تشغيله داخل كتلة معاملة، وما يزال يحتاج أقفالًا قصيرة في البداية والنهاية، لذا قد يتأخر بسبب المعاملات الطويلة.

إذا كان الفهرس موجودًا بسبب قيد PRIMARY KEY أو UNIQUE، فعادةً لا يمكنك حذفه مباشرة. يجب تعديل القيد عبر ALTER TABLE، والذي قد يأخذ أقفالًا أقوى. اعتبر ذلك عملية صيانة مخططة منفصلة.

إعادة تسمية الفهارس للوضوح

إعادة التسمية (ALTER INDEX ... RENAME TO ...) عادةً سريعة، لكن تجنّبها إذا كانت أدوات أو ترحيلات تشير إلى أسماء الفهارس مباشرة. عادةً أفضل عادة هي اختيار اسم واضح من البداية.

إذا كان الفهرس القديم لا يزال مطلوبًا

أحيانًا تحتاج أنماط استعلام مختلفة إلى فهارس مختلفة. إذا كانت استعلامات مهمة ما تزال تعتمد على القديم، احتفظ به. فكّر في تعديل الفهرس الجديد (ترتيب الأعمدة، شرط جزئي) بدلًا من إجبار الحذف.

راقب الأقفال وتقدّم البناء أثناء التشغيل

حوّل عادات إدارة قواعد البيانات إلى إصدارات
ابنِ أدوات داخلية باستخدام PostgreSQL ونشر التغييرات بخطة طرح وتراجع واضحة.
جرّب AppMaster

حتى مع CREATE INDEX CONCURRENTLY، يجب مراقبة ما يحدث بالزمن الحقيقي. معظم الحوادث المفاجئة تأتي من أمرين: جلسة حجب لم تلاحظها، أو معاملة طويلة تحبَس البناء قيد الانتظار.

اكتشاف الجلسات المحجوزة (من يعيق من)

ابدأ بإيجاد الجلسات التي تنتظر الأقفال:

SELECT
  a.pid,
  a.usename,
  a.application_name,
  a.state,
  a.wait_event_type,
  a.wait_event,
  now() - a.xact_start AS xact_age,
  left(a.query, 120) AS query
FROM pg_stat_activity a
WHERE a.wait_event_type = 'Lock'
ORDER BY xact_age DESC;

إذا احتجت إلى المعرّف الدقيق للحاجز، اتبعه من blocked_pid إلى blocking_pid:

SELECT
  blocked.pid  AS blocked_pid,
  blocking.pid AS blocking_pid,
  now() - blocked.xact_start AS blocked_xact_age,
  left(blocked.query, 80)  AS blocked_query,
  left(blocking.query, 80) AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
  ON blocking.pid = ANY (pg_blocking_pids(blocked.pid))
WHERE blocked.wait_event_type = 'Lock';

راقب تقدّم البناء وإشارات "التعليق"

يوفّر PostgreSQL تقدم بناء الفهرس. إذا رأيت لا حركة لفترة طويلة، ابحث عن معاملة طويلة (غالبًا جلسة خاملة تحمل لقطة قديمة).

SELECT
  pid,
  phase,
  lockers_total,
  lockers_done,
  blocks_total,
  blocks_done,
  tuples_total,
  tuples_done
FROM pg_stat_progress_create_index;

راقب أيضًا ضغط النظام: IO للقرص، تأخر النسخ التكراري، وارتفاع أزمنة الاستعلامات. البنيات المتزامنة ألطف على التوافر، لكنها ما تزال تقرأ الكثير من البيانات.

قواعد بسيطة تعمل جيدًا في الإنتاج:

  • انتظر إذا كان التقدّم مستمرًا وتأثير المستخدمين منخفض.
  • ألغِ وأعد الجدولة إذا كان البناء عالقًا خلف معاملة طويلة لا يمكنك إنهاؤها بأمان.
  • أوقف خلال ذروة الحركة إذا كان ضغط IO يؤثر على استعلامات العملاء.
  • أوقف الجلسة فقط كملاذ أخير وبعد التأكد مما تفعله.

للتواصل بين الفريق، اجعل التحديثات قصيرة: وقت البدء، المرحلة الحالية، ما الذي يُعوّق إن وُجد، وموعد التحقق التالي.

خطة التراجع: كيف تتراجع بأمان

اجعل البحث يبدو فوريًا
انشر تطبيق ويب على Vue3 يعمل بتناغم مع فهارس واستعلامات Postgres.
ابنِ تطبيق ويب

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

الطرق الشائعة لفشل عمل الفهرس

معظم حالات الفشل في الإنتاج متوقعة: البناء يصطدم بمهلة، أحدهم يلغيه أثناء حادث، الخادم ينخفض به قرص التخزين، أو البناء يتنافس مع الحركة العادية ليرتفع زمن استجابة المستخدمين.

مع CREATE INDEX CONCURRENTLY، الإلغاء عادةً آمن للتطبيق لأن الاستعلامات تستمر بالعمل. المقابل هو التنظيف: بناء مُلغى أو فاشل قد يترك فهرسًا غير صالحًا.

قواعد الإلغاء والتنظيف الآمن

إلغاء بناء متزامن لا يرجع كما تفعل المعاملات العادية. قد يترك PostgreSQL فهرسًا موجودًا لكنه غير صالح للمخطط.

-- Cancel the session building the index (use the PID you identified)
SELECT pg_cancel_backend(\u003cpid\u003e);

-- If the index exists but is invalid, remove it without blocking writes
DROP INDEX CONCURRENTLY IF EXISTS your_index_name;

قبل الحذف، تأكّد مما تنظر إليه:

SELECT
  c.relname AS index_name,
  i.indisvalid,
  i.indisready
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE c.relname = 'your_index_name';

إذا كانت indisvalid = false، فهو غير مستخدم وآمن للحذف.

قائمة فحص تراجع عملية عند استبدال فهرس قائم:

  • احتفظ بالفهرس القديم حتى يصبح الجديد مبنيًا وصالحًا تمامًا.
  • إذا فشل أو أُلغي البناء الجديد، احذف الفهرس الجديد غير الصالح بشكل متزامن.
  • إذا كنت قد حذفت الفهرس القديم بالفعل، أعد إنشاؤه بـ CREATE INDEX CONCURRENTLY لاستعادة الحالة السابقة.
  • إذا سبّب ضغط القرص الفشل، حرّر مساحة أولًا، ثم أعد المحاولة.
  • إذا تسببت المهلات بالفشل، جدولة نافذة أكثر هدوءًا بدلًا من الإصرار.

مثال عملي: تبدأ فهرسًا جديدًا لبحث إداري، يعمل 20 دقيقة ثم تظهر تنبيهات مساحة. ألغِ البناء، احذف الفهرس الجديد غير الصالح بشكل متزامن، واترك الفهرس القديم يخدم الحركة. يمكنك المحاولة مجددًا بعد تحرير المساحة، دون انقطاع يلاحظه المستخدمون.

أخطاء شائعة تخلق حوادث مفاجئة

معظم الحوادث حول الفهارس لا تحدث لأن PostgreSQL "بطيء." تحدث لأن تفصيلة صغيرة تحول تغييرًا آمنًا إلى تغيير محجوب.

1) وضع بناء متزامن داخل معاملة

CREATE INDEX CONCURRENTLY لا يمكن تشغيله داخل كتلة معاملة. العديد من أدوات الترحيل تغلف كل تغيير داخل معاملة افتراضيًا. النتيجة إما خطأ صريح (الأفضل) أو نشر فوضوي مع محاولات إعادة.

قبل تشغيل الترحيل، تأكد من أن أداتك تستطيع تشغيل عبارة منفصلة بدون معاملة خارجية، أو قسّم الترحيل إلى خطوة غير معاملة خاصة.

2) إطلاقه أثناء ذروة الحركة

تبني البنى المتزامنة يقلّل الحجب، لكنه ما يزال يضيف حملًا: قراءات إضافية وكتابات إضافية وزيادة ضغط autovacuum. بدء البناء أثناء نافذة نشر عندما ترتفع الحركة طريقة شائعة لخلق تباطؤ يبدو كحادث.

اختر فترة هادئة وتعامل معها كأي صيانة إنتاجية.

3) تجاهل المعاملات طويلة التشغيل

معاملة واحدة طويلة قد تمنع مرحلة التنظيف في بناء متزامن. قد يبدو الفهرس قيد التقدّم، ثم يقف قرب النهاية بينما ينتظر زوال لقطات قديمة.

كوّن عادة: افحص للمعاملات الطويلة قبل البدء، وراجعها مجددًا إذا توقف التقدّم.

4) حذف الشيء الخطأ (أو كسر قيد)

أحيانًا تحذف الفرق فهرسًا بالاسم من الذاكرة، أو تزيل فهرسًا يدعم قاعدة تفرد. إذا حذفت العنصر الخطأ، قد تفقد ضمانات (قواعد UNIQUE) أو تتراجع أداء الاستعلامات فورًا.

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

مثال واقعي: تسريع بحث إداري

أصدر بتفاجئات أقل
أعدّ قائمة تحقق قابلة للتكرار لتغييرات قاعدة البيانات وتحديثات التطبيق في مكان واحد.
ابدأ الآن

نقطة ألم شائعة هي بحث إداري يبدو فوريًا في staging لكنه زاحف في الإنتاج. لنفترض أن لديك جدول tickets كبيرًا (عشرات الملايين من الصفوف) خلف لوحة داخلية، والوكلاء كثيرًا ما يبحثون عن "التذاكر المفتوحة لعميل واحد، الأحدث أولًا."

الاستعلام يبدو هكذا:

SELECT id, customer_id, subject, created_at
FROM tickets
WHERE customer_id = $1
  AND status = 'open'
ORDER BY created_at DESC
LIMIT 50;

فهرس كامل على (customer_id, status, created_at) يساعد، لكنه يضيف حملًا على كل تحديث للتيكيت، بما في ذلك الصفوف المغلقة. إذا كانت معظم الصفوف ليست open، فالفهرس الجزئي غالبًا حل أبسط:

CREATE INDEX CONCURRENTLY tickets_open_by_customer_created_idx
ON tickets (customer_id, created_at DESC)
WHERE status = 'open';

خط زمني آمن في الإنتاج:

  • الفحص المسبق: تأكد من ثبات شكل الاستعلام وأن الجدول يملك مساحة قرص كافية لبناء فهرس جديد.
  • البناء: شغّل CREATE INDEX CONCURRENTLY في جلسة منفصلة مع إعدادات مهلات واضحة.
  • التحقق: شغّل ANALYZE tickets; وتأكد أن المخطط يستخدم الفهرس الجديد.
  • التنظيف: بمجرد أن تكون واثقًا، احذف أي فهرس قديم متكرر باستخدام DROP INDEX CONCURRENTLY.

مؤشرات النجاح:

  • ينخفض زمن البحث الإداري من ثوانٍ إلى عشرات الملليثانية لعملاء نموذجيين.
  • تبقى القراءات والكتابات العادية تعمل خلال البناء.
  • يرتفع استخدام CPU و IO أثناء البناء لكن يبقى ضمن حدود الأمان.
  • يمكنك عرض أرقام قبل/بعد واضحة: زمن الاستعلام، الصفوف الممسوحة، وتاريخ الأقفال.

قائمة سريعة وخطوات تالية

عمل الفهارس أكثر أمانًا عندما تعاملها كإصدار صغير للإنتاج: حضّر، راقبه أثناء التشغيل، ثم تحقق من النتيجة قبل التنظيف.

قبل البدء:

  • عيّن مهلات حتى لا يعلق قفل مفاجئ إلى الأبد.
  • تأكد من مساحة قرص حرة كافية لبناء الفهرس الجديد.
  • ابحث عن المعاملات طويلة التشغيل التي قد تبطئ البناء.
  • اختر نافذة حركة منخفضة وحدد ما يعنيه "الانتهاء".
  • اكتب خطة التراجع الآن.

أثناء التشغيل:

  • راقب الحجب وسلاسل انتظار الأقفال.
  • تتبع تقدّم البناء باستخدام pg_stat_progress_create_index.
  • راقب أعراض التطبيق: معدل الأخطاء، انتهاء المهلات، ونقاط النهاية البطيئة المرتبطة بالجدول.
  • كن مستعدًا للإلغاء إذا ارتفعت فترات انتظار الأقفال أو زيادات زمن الاستجابة للمستخدم.
  • سجّل ما حدث: وقت البدء، وقت الانتهاء، وأي تنبيهات.

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

إذا كررت هذا أكثر من مرة، اجعله خطوة توصيل قابلة للتكرار: كتيّب صغير للتشغيل، تجربة في staging ببيانات شبيهة بالإنتاج، ومالك واضح يراقب البناء.

إذا كنت تبني أدوات داخلية أو لوحات إدارة باستخدام AppMaster (appmaster.io)، فمفيدة أن تعامل تغييرات قاعدة البيانات مثل بناء الفهارس كجزء من نفس قائمة التحقق للإصدار: مقيسة، مُراقبة، ومع خطة تراجع يمكنك تنفيذها بسرعة.

الأسئلة الشائعة

لماذا قد يسبب إضافة أو تغيير فهرس توقفًا في الخدمة؟

عادةً ما يظهر التوقف كانتظار على الأقفال وليس كانقطاع كامل. يمكن أن تحجب عملية CREATE INDEX العادية عمليات الكتابة طوال مدة البناء، فتبدأ الطلبات التي تحتاج إلى إدراج أو تحديث أو حذف بالانتظار ثم انتهاء المهلة، مما يجعل الصفحات تتوقف وتتكدّس الطلبيات.

متى يجب أن أستخدم CREATE INDEX CONCURRENTLY بدلًا من CREATE INDEX العادي؟

استخدم CREATE INDEX CONCURRENTLY عندما يكون الجدول يخدم حركة حقيقية ولا يمكنك إيقاف الكتابات. هو الخيار الأكثر أمانًا للجداول الكبيرة أو المشغولة، رغم أنه أبطأ ويمكن أن يتأخر بسبب المعاملات الطويلة.

هل يعني CONCURRENTLY "لا أقفال إطلاقًا"؟

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

لماذا تفشل نصيحة "فقط شغّله ليلًا" في كثير من الأحيان؟

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

ما الذي يجب أن أتحقق منه قبل تشغيل بناء فهرس متزامن في الإنتاج؟

أولًا، تأكد من أنك لست داخل معاملة؛ CREATE INDEX CONCURRENTLY سيفشل داخل BEGIN. ثانيًا، تحقق من توفر مساحة قرص كافية للفهرس الجديد زائد مساحة عمل مؤقتة. ثالثًا، عيّن lock_timeout قصيرًا لتفشل بسرعة إذا لم تستطع الحصول على الأقفال المطلوبة.

ما هي مهلات الوقت التي يجب أن أضعها لتغييرات الفهارس الآمنة؟

نقطة بداية شائعة هي تشغيل في نفس الجلسة: SET lock_timeout = '2s'; وSET statement_timeout = '0';. هذا يمنعك من الانتظار إلى أجل غير مسمى على الأقفال دون قتل الجلسة أثناء البناء بسبب مهلة بيان متشددة.

كيف أعرف أن بناء فهرس متزامن عالق، وما الذي أبحث عنه أولاً؟

ابدأ بـ pg_stat_progress_create_index لترى المرحلة وما إذا كانت الكتل والصفوف تتقدّم. إذا توقفت التقدّم، افحص pg_stat_activity لأجل انتظار الأقفال وابحث عن المعاملات طويلة العمر، خاصة الجلسات الخاملة التي تحمل لقطات قديمة.

ما هي الطريقة الأكثر أمانًا لاستبدال فهرس قائم دون حجب الحركة؟

أنشئ الفهرس الجديد باستخدام CREATE INDEX CONCURRENTLY، تحقق أن المخطط يمكنه استخدامه (وأن توقيت الاستعلامات تحسّن)، ثم أسقط الفهرس القديم بـ DROP INDEX CONCURRENTLY فقط بعد التأكد. هذا الترتيب "أضف أولًا، ثم أزل لاحقًا" يحفظ نقطة ارتداد عاملة.

هل يمكنني دائمًا إسقاط فهرس بشكل متزامن؟

DROP INDEX CONCURRENTLY آمن عادةً للفهارس العادية، لكنه يحتاج نوافذ أقفال قصيرة ولا يمكن تشغيله داخل كتلة معاملة. إذا كان الفهرس يدعم قيدًا كـ PRIMARY KEY أو UNIQUE فعلى الأغلب يجب تعديل القيد عبر ALTER TABLE، وهذا قد يتطلّب أقفالًا أقوى وتخطيطًا مسبقًا.

كيف أستعيد الوضع بأمان إذا فشل أو أُلغي بناء فهرس متزامن؟

ألغِ جلسة البناء أولًا، ثم افحص ما إذا تُرك فهرس غير صالح. إذا كانت قيمة indisvalid = false، فاسقطه بـ DROP INDEX CONCURRENTLY واحتفظ بالفهرس القديم. إذا أزلت القديم بالفعل، أعد إنشاؤه باستخدام CREATE INDEX CONCURRENTLY لاستعادة الحالة السابقة.

من السهل أن تبدأ
أنشئ شيئًا رائعًا

تجربة مع AppMaster مع خطة مجانية.
عندما تكون جاهزًا ، يمكنك اختيار الاشتراك المناسب.

البدء