أقفال استشارية رائعة وأين يعيشون

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



اقرأ المزيد عن الأقفال

مزايا أقفال التوصية


الفرق الأساسي بين هذه الآلية وأقفال مستوى الجدول / الصفحة / السجل "العادي" هو أن هناك العديد من الميزات الرئيسية.

قفل معرف مخصص


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

بالمناسبة ، يعني إرفاق كل قفل بعملية أنه من خلال تسميته من خلال الاتصال pg_terminate_backend(pid)أو إكماله بشكل صحيح من جانب العميل ، يمكنك التخلص من جميع الأقفال التي يفرضها.

تحقق CAS من التقاط القفل


CAS هي عبارة عن مقارنة وتعيين ، أي التحقق من قدرة الالتقاط والتقاط القفل نفسه يتم كعملية ذرية واحدة ، ومن الواضح أنه لا يمكن لأحد "الإسفين" بينهما.

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

لا التقاط بدون استثناءات وتوقعات


توجد أقفال "عادية" في نموذج "إذا طلبت قفل بالفعل ، فانتظر. إذا كنت لا تريد الانتظار ( NOWAIT، statement_timeout، lock_timeout) - فإليك استثناء " . يتداخل هذا النهج بشكل كبير داخل المعاملة ، لأنه بعد ذلك يجب عليك إما تنفيذ كتلة BEGIN-EXCEPTION-ENDللمعالجة أو استرجاع ( ROLLBACK) المعاملة.

الطريقة الوحيدة لتجنب هذا السلوك هي استخدام التصميم SELECT ... SKIP LOCKEDالمرفق مع الإصدار 9.5. لسوء الحظ ، باستخدام هذه الطريقة ، لا يمكن تمييز الخيارين "لم يكن لديك ما يجب حظره على الإطلاق" و "تم حظره بالفعل".

الأقفال الموصى بها التي تسببها وظائف المحاولة تعود ببساطة TRUE/FALSE.
لا تخلط بين pg_advisory_lockو - الوظيفة الأولى سوف ننتظر حتى يتلقى قفل، وسوف الثانية ببساطة العودة فورا إذا كان من المستحيل للاستيلاء عليها "في الوقت الراهن".pg_try_advisory_lockFALSE

أقفال داخل وبعد المعاملة


كما ذكرت أعلاه ، يتم تأمين أقفال الكائن بالعملية وهي موجودة فقط كجزء من المعاملة الحالية فيها. حتى لمجرد فرض - لن تنجح:

LOCK TABLE tbl;
-- ERROR:  LOCK TABLE can only be used in transaction blocks

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

SELECT pg_advisory_lock(1);
SELECT * FROM pg_locks WHERE pid = pg_backend_pid() AND locktype = 'advisory';

-[ RECORD 1 ]------+--------------
locktype           | advisory
database           | 263911484
relation           |
page               |
tuple              |
virtualxid         |
transactionid      |
classid            | 0 <--  int4 #1    int8
objid              | 1 <--  int4 #2    int8
objsubid           | 1
virtualtransaction | 416/475768
pid                | 29264
mode               | ExclusiveLock
granted            | t
fastpath           | f

ولكن بالفعل من الإصدار 9.1 ، ظهرت إصدارات xact من الوظائف الاستشارية التي تسمح لك بتنفيذ سلوك الأقفال "العادية" التي يتم تحريرها تلقائيًا عند اكتمال المعاملة التي فرضتها.

أمثلة للاستخدام في VLSI


في الواقع ، مثل أي قفل آخر ، تعمل الاستشارات لضمان تفرد معالجة المورد. في بلدنا ، عادة ما تكون هذه الموارد إما الجدول بأكمله أو سجل معين للجدول ، والذي لسبب ما لا يريد "تأمينه".

معالجة أحادية للعامل


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

للقيام بذلك ، سنحاول قفل معرف الجدول كمعلمة أولى ومعرف معالجة التطبيق المحدد كالثاني:

SELECT pg_try_advisory_lock(
  'processed_table'::regclass::oid
, -1 --   worker'
);

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

معالجة قائمة الانتظار المتزامنة


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

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

في هذه الحالة ، نفرض قفلًا على معرف الجدول و PK لسجل معين:

SELECT
  *
FROM
  queue_table
WHERE
  pg_try_advisory_lock('queue_table'::regclass::oid, pk_id)
ORDER BY
  pk_id
LIMIT 1;

أي أن العملية ستحصل من الجدول على أول سجل لم يتم حظره بعد من قبل أشقائها المتنافسين.

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

مهم! لا تنس الحفاظ بشكل دوري على قائمة الانتظار الخاصة بك بشكل صحيح !

معالجة حصرية للوثائق


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

القضايا التقليدية


أين بدونهم! يتلخص كل شيء تقريبًا في شيء واحد: لم يفتحوا ما قفلوه .

تراكب متعدد لقفل استشاري واحد


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

ضع الكثير من الأقفال في وقت واحد




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

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

تسرب أثناء تصفية السجلات


هنا نأخذ الطلب السابق ونضيف حالة غير ضارة مثل معرف التحقق من التماثل - AND pk_id % 2 = 0. سيتم التحقق من كلا الشرطين لكل إدخال! ونتيجة لذلك ، تم pg_try_advisory_lockالانتهاء منه ، وتم فرض القفل ، ثم تم تصفية السجل بالتساوي.



أو خيار من الدليل:
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- !

هذا كل شيء - لا يزال الحظر قائماً ، لكننا لسنا على علم بذلك. يتم التعامل مع الطلبات الصحيحة ، في أسوأ الحالات - pg_advisory_unlock_all.

أوه ، مرتبك!


كلاسيكيات هذا النوع ...

الخلط و ، ونتساءل لماذا كان يعمل لفترة طويلة. ولكن لأن الإصدار غير المحاولة ينتظر . الخلط و ، ويتساءل أين ذهبت القفل - وذلك "انتهى" مع هذه الصفقة. وتتكون المعاملة من طلب واحد ، لأنه لم يتم الإعلان عنها "صراحة" في أي مكان ، نعم.pg_try_advisory_lockpg_advisory_lock

pg_try_advisory_lockpg_try_advisory_xact_lock

العمل من خلال pgbouncer


هذا مصدر منفصل للألم للكثيرين عندما ، من أجل الأداء ، يمر العمل مع قاعدة البيانات من خلال pgbouncer في وضع المعاملة .

هذا يعني أنه يمكن تنفيذ اثنين من معاملاتك المجاورة التي تعمل على نفس الاتصال بقاعدة البيانات (التي تمر فعليًا عبر pgbouncer) في اتصالات "مادية" مختلفة على جانب قاعدة البيانات. ولديهم أقفال خاصة بهم ... لكل منها



خيارات قليلة:

  • أو انتقل إلى العمل من خلال اتصال مباشر بقاعدة البيانات
  • أو اختراع خوارزمية بحيث تكون جميع الأقفال الاستشارية داخل المعاملة فقط (xact)

هذا كل شئ حتى الان.

All Articles