أقفال في بوستجرس: 1. أقفال العلاقة

ركزت السلسلة السابقة من المقالات على العزلة والتعددية والصحافة .

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

ستتألف الدورة من أربعة أجزاء:

  1. أقفال العلاقة (هذه المقالة) ؛
  2. أقفال الصف ؛
  3. أقفال الأشياء الأخرى والأقفال المسند ؛
  4. أقفال في ذاكرة الوصول العشوائي .

تستند مواد جميع المقالات إلى دورات تدريبية إدارية أجريها أنا وبافيل بلوزانوف ، لكن لا تكررها حرفيًا وتهدف إلى القراءة المدروسة والتجريب المستقل.



معلومات عامة عن الأقفال


يستخدم PostgreSQL العديد من الآليات المختلفة التي يتم استخدامها لمنع شيء ما (أو على الأقل تسمى ذلك). لذلك ، سأبدأ بكلمات عامة حول سبب الحاجة إلى الأقفال على الإطلاق ، ما هي وكيف تختلف عن بعضها البعض. ثم سنرى ما هو هذا التنوع الموجود في PostgreSQL وفقط بعد ذلك سنبدأ في التعامل مع أنواع مختلفة من الأقفال بالتفصيل.

تستخدم الأقفال لتبسيط الوصول المتزامن إلى الموارد المشتركة.

يشير الوصول التنافسي إلى الوصول المتزامن لعدة عمليات. يمكن إجراء العمليات نفسها بشكل متوازٍ (إذا سمحت المعدات بذلك) وبالتتابع في وضع مشاركة الوقت - وهذا ليس بالأمر المهم.

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

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

على مستوى منخفض ، يتم تمثيل القفل بواسطة قسم من الذاكرة المشتركة ، يُلاحظ بطريقة أو بأخرى ما إذا كان القفل مجانيًا أو تم التقاطه (وربما يتم تسجيل معلومات إضافية: رقم العملية ، وقت الالتقاط ، إلخ).

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

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

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

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

من حيث المبدأ ، يمكن أن يكون المورد المحمي أي شيء ، إذا كان يمكن تحديد هذا المورد فقط بشكل لا لبس فيه ومطابقته بعنوان حظر.

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

تتأثر فعالية الأقفال بعدة عوامل ، نميزها بين اثنين.

  • تحبب (التفاصيل) أهمية إذا كانت الموارد تشكل تسلسل هرمي.

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

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

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

وفقا لوقت الاستخدام ، يمكن تقسيم الأقفال إلى طويلة وقصيرة.

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

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

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

يستخدم PostgreSQL أنواعًا مختلفة من الأقفال.

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

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

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

أقفال قصيرة تشمل أقفال مختلفة من الهياكل RAM . سننظر فيها في المادة الأخيرة من الدورة.

أقفال الكائن


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

صياغة ضبابية؟ إنه بسبب استخدام الأقفال من هذه المجموعة لأغراض متعددة. ما يوحدهم هو كيف يتم ترتيبها.

جهاز


توجد أقفال الكائنات في الذاكرة المشتركة للخادم. يقتصر عددها على ناتج قيم المعلمتين: max_locks_per_transaction × max_connections .

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

يمكن الاطلاع على جميع الأقفال في طريقة العرض pg_locks.

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

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

أنواع الكائنات


فيما يلي قائمة بأنواع الأقفال (أو ، إذا أردت ، أنواع الكائنات) التي سنتعامل معها في هذه المقالة والمقال التالي. يتم إعطاء الأسماء وفقًا للعمود locktype في طريقة العرض pg_locks.

  • علاقة

    أقفال العلاقة.
  • معاملة و virtualxid

    حظر رقم المعاملة (حقيقي أو افتراضي). كل معاملة في حد ذاتها تحمل قفلًا حصريًا لرقمها ، لذلك فإن هذه الأقفال ملائمة للاستخدام عندما تحتاج إلى الانتظار حتى نهاية معاملة أخرى.
  • الصفوف (tuple)

    قفل نسخة السلسلة. يتم استخدامه في بعض الحالات لتعيين الأولوية بين العديد من المعاملات التي تتوقع تأمين نفس الصف.

سوف نؤجل مناقشة الأنواع المتبقية من الأقفال حتى المادة الثالثة في الدورة. يتم التقاطها جميعًا إما في وضع استثنائي أو حصري ومشترك.

  • مد

    يستخدم عند إضافة الصفحات إلى ملف بأي علاقة.
  • موضوع

    تأمين الكائنات التي ليست علاقات (قواعد البيانات والمخططات والاشتراكات ، وما إلى ذلك).
  • صفحة

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

    حظر الموصى بها ، تعيين يدويا من قبل المستخدم.

أقفال العلاقة


لكي لا أفقد السياق ، سأضع علامة على هذه الأنواع من الأقفال ، والتي سيتم مناقشتها لاحقًا.



وسائط


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

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

وضع القفلASRSREسوSSREEAEأوامر SQL عينة
حصة الوصولXSELECT
حصة الصفXXاختر للتحديث / SHARE
الحصري الصفXXXXإدراج ، تحديث ، حذف
حصة التحديث الحصريXXXXXفراغ ، تغيير الجدول * ، إنشاء فهرس باستمرار
سهمXXXXXإنشاء مؤشر
حصة الصف الحصريXXXXXXإنشاء TRIGGER ، تغيير الجدول *
باستثناءXXXXXXXتحديث مات. عرض حاليا
الوصول الحصريXXXXXXXXإسقاط ، اقتطاع ، فراغ كامل ، قفل الجدول ، تغيير الجدول * ، تحديث مات. VIEW

بعض التعليقات:

  • تسمح أوضاع الأربعة الأولى بتغييرات البيانات المتزامنة في الجدول ، ولا تسمح الأشكال الأربعة التالية بذلك.
  • الوضع الأول (Access Access) هو الأضعف ، وهو متوافق مع أي وضع آخر غير (Access Exclusive). هذا الوضع الأخير حصري ، وهو غير متوافق مع أي وضع.
  • يحتوي الأمر ALTER TABLE على العديد من الخيارات ، والتي تتطلب مستويات مختلفة من التأمين. لذلك ، في المصفوفة ، يظهر هذا الأمر في خطوط مختلفة ويتم تمييزه بعلامة نجمية.

على سبيل المثال ، على سبيل المثال


أعط مثالا ماذا يحدث إذا قمت بتشغيل CREATE INDEX؟

نجد في الوثائق التي يحددها هذا الأمر القفل في وضع المشاركة. وفقًا للمصفوفة ، نحدد أن الأمر متوافق مع نفسه (أي ، يمكنك إنشاء فهارس متعددة في وقت واحد) ومع أوامر القراءة. وبالتالي ، سوف تستمر أوامر SELECT في العمل ، ولكن سيتم حظر أوامر UPDATE و DELETE و INSERT.

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

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

=> CREATE TABLE accounts( acc_no integer PRIMARY KEY, amount numeric ); => INSERT INTO accounts VALUES (1,1000.00), (2,2000.00), (3,3000.00); 

في الجلسة الثانية ، ابدأ المعاملة. نحن بحاجة إلى رقم عملية الخدمة.

 | => SELECT pg_backend_pid(); 
 | pg_backend_pid | ---------------- | 4746 | (1 row) 

ما الأقفال التي تعقدها المعاملة التي بدأت حديثًا؟ نحن ننظر في pg_locks:

 => SELECT locktype, relation::REGCLASS, virtualxid AS virtxid, transactionid AS xid, mode, granted FROM pg_locks WHERE pid = 4746; 
  locktype | relation | virtxid | xid | mode | granted ------------+----------+---------+-----+---------------+--------- virtualxid | | 5/15 | | ExclusiveLock | t (1 row) 

كما قلت سابقًا ، تحمل الصفقة دائمًا قفلًا حصريًا (ExclusiveLock) برقمها الخاص ، وفي هذه الحالة ، يكون هناك رقم افتراضي. لا توجد أقفال أخرى على هذه العملية.

الآن تحديث صف الجدول. كيف سيتغير الوضع؟

 | => UPDATE accounts SET amount = amount + 100 WHERE acc_no = 1; 

 => \g 
  locktype | relation | virtxid | xid | mode | granted ---------------+---------------+---------+--------+------------------+--------- relation | accounts_pkey | | | RowExclusiveLock | t relation | accounts | | | RowExclusiveLock | t virtualxid | | 5/15 | | ExclusiveLock | t transactionid | | | 529404 | ExclusiveLock | t (4 rows) 

يوجد الآن أقفال على الجدول القابل للتعديل والفهرس (تم إنشاؤه للمفتاح الأساسي) ، والذي يتم استخدامه بواسطة أمر UPDATE. تؤخذ كل من الأقفال في وضع RowExclusiveLock. بالإضافة إلى ذلك ، تمت إضافة حظر حصري لرقم المعاملة الحقيقي (والذي ظهر بمجرد أن بدأت المعاملة في تغيير البيانات).

الآن في جلسة أخرى ، سنحاول إنشاء فهرس على جدول.

 || => SELECT pg_backend_pid(); 
 || pg_backend_pid || ---------------- || 4782 || (1 row) 
 || => CREATE INDEX ON accounts(acc_no); 

يتجمد الأمر تحسبا لإصدار المصدر. ما نوع القفل الذي تحاول التقاطه؟ تحقق:

 => SELECT locktype, relation::REGCLASS, virtualxid AS virtxid, transactionid AS xid, mode, granted FROM pg_locks WHERE pid = 4782; 
  locktype | relation | virtxid | xid | mode | granted ------------+----------+---------+-----+---------------+--------- virtualxid | | 6/15 | | ExclusiveLock | t relation | accounts | | | ShareLock | f (2 rows) 

نرى أن المعاملة تحاول الحصول على قفل الجدول في وضع ShareLock ، لكن لا يمكن (منح = و).

من السهل العثور على عدد عملية الحظر ، وبصفة عامة عدة أرقام ، باستخدام الوظيفة التي ظهرت في الإصدار 9.6 (قبل ذلك كان علي استخلاص استنتاجات من خلال النظر بعناية في جميع محتويات pg_locks):

 => SELECT pg_blocking_pids(4782); 
  pg_blocking_pids ------------------ {4746} (1 row) 

ومن ثم ، لفهم الموقف ، يمكنك الحصول على معلومات حول الجلسات ، والتي تتضمن الأرقام الموجودة:

 => SELECT * FROM pg_stat_activity WHERE pid = ANY(pg_blocking_pids(4782)) \gx 
 -[ RECORD 1 ]----+------------------------------------------------------------ datid | 16386 datname | test pid | 4746 usesysid | 16384 usename | student application_name | psql client_addr | client_hostname | client_port | -1 backend_start | 2019-08-07 15:02:53.811842+03 xact_start | 2019-08-07 15:02:54.090672+03 query_start | 2019-08-07 15:02:54.10621+03 state_change | 2019-08-07 15:02:54.106965+03 wait_event_type | Client wait_event | ClientRead state | idle in transaction backend_xid | 529404 backend_xmin | query | UPDATE accounts SET amount = amount + 100 WHERE acc_no = 1; backend_type | client backend 

بعد اكتمال المعاملة ، يتم إصدار الأقفال ويتم إنشاء الفهرس.

 | => COMMIT; 
 | COMMIT 

 || CREATE INDEX 

في الطابور! ..


من أجل تخيل أفضل لما يؤدي إليه ظهور قفل غير متوافق ، سوف نرى ما يحدث إذا تم تنفيذ أمر VACUUM FULL أثناء تشغيل النظام.

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

 => BEGIN; => SELECT * FROM accounts; 
  acc_no | amount --------+--------- 2 | 2000.00 3 | 3000.00 1 | 1100.00 (3 rows) 
 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+-----------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} (1 row) 

ثم يقوم المسؤول بتنفيذ الأمر VACUUM FULL ، والذي يتطلب تأمين مستوى الوصول الحصري ، غير متوافق مع أي شيء ، حتى مع وصول المشاركة. (يتطلب الأمر LOCK TABLE أيضًا نفس القفل.) قوائم انتظار المعاملة.

 | => BEGIN; | => LOCK TABLE accounts; --  VACUUM FULL 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} relation | AccessExclusiveLock | f | 4746 | {4710} (2 rows) 

لكن التطبيق يستمر في إصدار الطلبات ، والآن يظهر الأمر SELECT في النظام. من الناحية النظرية البحتة ، كان يمكن أن "تنزلق" بينما تنتظر VACUUM FULL ، لكن لا - إنها بصراحة تأخذ مكانًا في قائمة الانتظار لـ VACUUM FULL.

 || => SELECT * FROM accounts; 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessShareLock | t | 4710 | {} relation | AccessExclusiveLock | f | 4746 | {4710} relation | AccessShareLock | f | 4782 | {4746} (3 rows) 

بعد اكتمال المعاملة الأولى مع أمر SELECT وإطلاق القفل ، يبدأ الأمر VACUUM FULL (الذي قمنا بمحاكاته باستخدام أمر LOCK TABLE).

 => COMMIT; 
 COMMIT 

 | LOCK TABLE 

 => SELECT locktype, mode, granted, pid, pg_blocking_pids(pid) AS wait_for FROM pg_locks WHERE relation = 'accounts'::regclass; 
  locktype | mode | granted | pid | wait_for ----------+---------------------+---------+------+---------- relation | AccessExclusiveLock | t | 4746 | {} relation | AccessShareLock | f | 4782 | {4746} (2 rows) 

وفقط بعد إتمام VACUUM FULL لعمله وإزالة القفل ، ستكون جميع الأوامر المتراكمة في قائمة الانتظار (SELECT في مثالنا) قادرة على التقاط الأقفال المقابلة (Access Share) وتنفيذها.

 | => COMMIT; 
 | COMMIT 

 || acc_no | amount || --------+--------- || 2 | 2000.00 || 3 | 3000.00 || 1 | 1100.00 || (3 rows) 

وبالتالي ، يمكن لأمر غير دقيق أن يشل تشغيل النظام لفترة أطول بكثير من وقت تنفيذ الأمر نفسه.

أدوات الرصد


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

لقد تعرفنا بالفعل على طريقة واحدة: في لحظة القفل الطويل ، يمكننا تنفيذ طلب إلى طريقة العرض pg_locks ، والنظر في المعاملات القابلة للقفل والحظر (وظيفة pg_blocking_pids) وفك تشفيرها باستخدام pg_stat_activity.

هناك طريقة أخرى لتمكين المعلمة log_lock_waits . في هذه الحالة ، ستظهر المعلومات في سجل رسائل الخادم إذا كانت المعاملة في انتظار فترة أطول من deadlock_timeout (على الرغم من استخدام المعلمة لـ deadlocks ، فإننا نتحدث عن التوقعات العادية).

لنجربها.

 => ALTER SYSTEM SET log_lock_waits = on; => SELECT pg_reload_conf(); 

قيمة المعلمة deadlock_timeout الافتراضية هي ثانية واحدة:

 => SHOW deadlock_timeout; 
  deadlock_timeout ------------------ 1s (1 row) 

لعب القفل.

 => BEGIN; => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 
 UPDATE 1 

 | => BEGIN; | => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

يتوقع الأمر UPDATE الثاني تأمينًا. انتظر ثانية وأكمل المعاملة الأولى.

 => SELECT pg_sleep(1); => COMMIT; 
 COMMIT 

الآن يمكن إكمال المعاملة الثانية.

 | UPDATE 1 
 | => COMMIT; 
 | COMMIT 

وجميع المعلومات الهامة حصلت في المجلة:

 postgres$ tail -n 7 /var/log/postgresql/postgresql-11-main.log 
 2019-08-07 15:26:30.827 MSK [5898] student@test LOG: process 5898 still waiting for ShareLock on transaction 529427 after 1000.186 ms 2019-08-07 15:26:30.827 MSK [5898] student@test DETAIL: Process holding the lock: 5862. Wait queue: 5898. 2019-08-07 15:26:30.827 MSK [5898] student@test CONTEXT: while updating tuple (0,4) in relation "accounts" 2019-08-07 15:26:30.827 MSK [5898] student@test STATEMENT: UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 
 2019-08-07 15:26:30.836 MSK [5898] student@test LOG: process 5898 acquired ShareLock on transaction 529427 after 1009.536 ms 2019-08-07 15:26:30.836 MSK [5898] student@test CONTEXT: while updating tuple (0,4) in relation "accounts" 2019-08-07 15:26:30.836 MSK [5898] student@test STATEMENT: UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 1; 

أن تستمر .

Source: https://habr.com/ru/post/ar462877/


All Articles