MVCC في PostgreSQL-8. تجمد

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

ثم فحصنا أنواعًا مختلفة من التنظيف: داخل الصفحة (جنبًا إلى جنب مع تحديثات HOT) ، منتظمة وتلقائية .

وحصلت على الموضوع الأخير من هذه الدورة. اليوم سنتحدث عن مشكلة ملف تعريف المعاملة الملفوفة والتجميد.

تجاوز عداد المعاملات


يحتوي PostgreSQL على 32 بتًا لرقم المعاملة. هذا رقم كبير إلى حد ما (حوالي 4 مليارات دولار) ، ولكن مع التشغيل النشط للخادم ، فقد يتم استنفاده. على سبيل المثال ، عند حمولة 1000 معاملة في الثانية الواحدة ، سيحدث هذا بعد شهر ونصف فقط من التشغيل المستمر.

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



لماذا لا يتم تخصيص 64 بت لرقم المعاملة - لأن هذا سيؤدي إلى حل المشكلة تمامًا؟ الحقيقة هي أنه (كما تمت مناقشته سابقًا ) في رأس كل نسخة من السطر يتم تخزين رقمين للمعاملات - xmin و xmax. حجم الرأس كبير جدًا بالفعل ، لا يقل عن 23 بايت ، وستؤدي الزيادة في عمق البت إلى زيادته بمقدار 8 بايتات أخرى. هذا ليس بأي حال من الأحوال.

يتم تطبيق أرقام المعاملات 64 بت في منتج شركتنا ، Postgres Pro Enterprise ، لكنها ليست صادقة تمامًا سواء: xmin و xmax يظلان 32 بت ، وعنوان الصفحة يحتوي على صفحة "بداية عصر" مشتركة.

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

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



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



تجميد الإصدار وقواعد الرؤية


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



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

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

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

=> CREATE TABLE tfreeze( id integer, s char(300) ) WITH (fillfactor = 10, autovacuum_enabled = off); 

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

 => CREATE FUNCTION heap_page(relname text, pageno_from integer, pageno_to integer) RETURNS TABLE(ctid tid, state text, xmin text, xmin_age integer, xmax text, t_ctid tid) AS $$ SELECT (pageno,lp)::text::tid AS ctid, CASE lp_flags WHEN 0 THEN 'unused' WHEN 1 THEN 'normal' WHEN 2 THEN 'redirect to '||lp_off WHEN 3 THEN 'dead' END AS state, t_xmin || CASE WHEN (t_infomask & 256+512) = 256+512 THEN ' (f)' WHEN (t_infomask & 256) > 0 THEN ' (c)' WHEN (t_infomask & 512) > 0 THEN ' (a)' ELSE '' END AS xmin, age(t_xmin) xmin_age, t_xmax || CASE WHEN (t_infomask & 1024) > 0 THEN ' (c)' WHEN (t_infomask & 2048) > 0 THEN ' (a)' ELSE '' END AS xmax, t_ctid FROM generate_series(pageno_from, pageno_to) p(pageno), heap_page_items(get_raw_page(relname, pageno)) ORDER BY pageno, lp; $$ LANGUAGE SQL; 

يرجى ملاحظة أن علامة التجميد (التي نعرضها بالحرف f بين قوسين) تحددها التثبيت المتزامنة للمطالبات الملتزمة والمجهضة. تذكر العديد من المصادر (بما في ذلك الوثائق) الرقم الخاص FrozenTransactionId = 2 ، والذي يمثل المعاملات المجمدة. عمل هذا النظام حتى الإصدار 9.4 ، ولكن الآن تم استبداله بت وحدات تلميح الأدوات - وهذا يسمح لك بحفظ رقم المعاملة الأصلي في إصدار السطر ، وهو مناسب لأغراض الدعم وتصحيح الأخطاء. ومع ذلك ، يمكن أن تحدث المعاملات التي تتم برقم 2 في الأنظمة الأقدم ، حتى تتم ترقيتها إلى أحدث الإصدارات.

نحتاج أيضًا إلى ملحق pg_visibility ، والذي يسمح لك بالاطلاع على خريطة الرؤية:

 => CREATE EXTENSION pg_visibility; 

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

بدءًا من الإصدار 9.6 ، تمت إضافة خريطة تجميد إلى نفس الطبقة - بت واحد إضافي لكل صفحة. تحدد خريطة التجميد الصفحات التي يتم فيها تجميد جميع إصدارات الصفوف.

نقوم بإدراج عدة صفوف في الجدول ونقوم بالتنظيف على الفور لإنشاء خريطة رؤية:

 => INSERT INTO tfreeze(id, s) SELECT g.id, 'FOO' FROM generate_series(1,100) g(id); => VACUUM tfreeze; 

ونحن نرى أنه يتم الآن وضع علامة على كلتا الصفحتين في خريطة الرؤية (all_visible) ، ولكن لم يتم تجميدها بعد (all_frozen):

 => SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno; 
  blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows) 

عمر المعاملة التي أنشأت الصفوف (xmin_age) هي 1 - هذه هي آخر معاملة تم تنفيذها على النظام:

 => SELECT * FROM heap_page('tfreeze',0,1); 
  ctid | state | xmin | xmin_age | xmax | t_ctid -------+--------+---------+----------+-------+-------- (0,1) | normal | 697 (c) | 1 | 0 (a) | (0,1) (0,2) | normal | 697 (c) | 1 | 0 (a) | (0,2) (1,1) | normal | 697 (c) | 1 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 1 | 0 (a) | (1,2) (4 rows) 

الحد الأدنى لسن التجميد


هناك ثلاثة عوامل رئيسية تتحكم في التجمد ، وسنتناولها بدورها.

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

تحدد القيمة الافتراضية لهذه المعلمة أن المعاملات تبدأ في التجميد بعد مرور 50 مليون معاملة أخرى منذ ظهورها:

 => SHOW vacuum_freeze_min_age; 
  vacuum_freeze_min_age ----------------------- 50000000 (1 row) 

لمعرفة كيفية حدوث التجميد ، نقوم بتقليل قيمة هذه المعلمة إلى الوحدة.

 => ALTER SYSTEM SET vacuum_freeze_min_age = 1; => SELECT pg_reload_conf(); 

وسنقوم بتحديث سطر واحد في صفحة الصفر. سيصل الإصدار الجديد إلى نفس الصفحة نظرًا لقيمة fillfactor الصغيرة.

 => UPDATE tfreeze SET s = 'BAR' WHERE id = 1; 

إليك ما نراه الآن في صفحات البيانات:

 => SELECT * FROM heap_page('tfreeze',0,1); 
  ctid | state | xmin | xmin_age | xmax | t_ctid -------+--------+---------+----------+-------+-------- (0,1) | normal | 697 (c) | 2 | 698 | (0,3) (0,2) | normal | 697 (c) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (c) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 2 | 0 (a) | (1,2) (5 rows) 

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

 => SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno; 
  blkno | all_visible | all_frozen -------+-------------+------------ 0 | f | f 1 | t | f (2 rows) 

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

 => VACUUM tfreeze; => SELECT * FROM heap_page('tfreeze',0,1); 
  ctid | state | xmin | xmin_age | xmax | t_ctid -------+---------------+---------+----------+-------+-------- (0,1) | redirect to 3 | | | | (0,2) | normal | 697 (f) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 (c) | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (c) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 2 | 0 (a) | (1,2) (5 rows) 

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

 => SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno; 
  blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows) 

العمر لتجميد الجدول بأكمله


لتجميد إصدار الخطوط المتبقية في الصفحات التي لا ينظر إليها التنظيف فقط ، يتم توفير معلمة ثانية: vacuum_freeze_table_age . إنه يحدد عمر المعاملة ، حيث يتجاهل التنظيف خريطة الرؤية وينتقل عبر كل صفحات الجدول إلى التجميد.

يخزن كل جدول رقم معاملة ، ومن المعروف أنه يتم ضمان تجميد كافة المعاملات القديمة (pg_class.relfrozenxid). مع عمر هذه المعاملة التي تم تذكرها ، تتم مقارنة قيمة المعلمة vacuum_freeze_table_age .

 => SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze'; 
  relfrozenxid | age --------------+----- 694 | 5 (1 row) 

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

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

بطريقة أو بأخرى ، يتم تجميد جميع الصفحات في الجدول مرة واحدة في المعاملات ( vacuum_freeze_table_age - vacuum_freeze_min_age ). مع القيم الافتراضية ، يحدث هذا مرة واحدة لكل مليون معاملة:

 => SHOW vacuum_freeze_table_age; 
  vacuum_freeze_table_age ------------------------- 150000000 (1 row) 

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

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

 => ALTER SYSTEM SET vacuum_freeze_table_age = 5; => SELECT pg_reload_conf(); 

لننظف

 => VACUUM tfreeze; 

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

 => SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze'; 
  relfrozenxid | age --------------+----- 698 | 1 (1 row) 

الآن تم تجميد جميع إصدارات الخطوط في الصفحة الأولى:

 => SELECT * FROM heap_page('tfreeze',0,1); 
  ctid | state | xmin | xmin_age | xmax | t_ctid -------+---------------+---------+----------+-------+-------- (0,1) | redirect to 3 | | | | (0,2) | normal | 697 (f) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 (c) | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (f) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (f) | 2 | 0 (a) | (1,2) (5 rows) 

بالإضافة إلى ذلك ، تم وضع علامة على الصفحة الأولى في خريطة التجميد:

 => SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno; 
  blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | t (2 rows) 

عصر الاستجابة "العدوانية"


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

ماذا يمكن أن يكون السبب في ذلك؟ هناك أسباب مختلفة.

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

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

القيمة الافتراضية متحفظة إلى حد ما:

 => SHOW autovacuum_freeze_max_age; 
  autovacuum_freeze_max_age --------------------------- 200000000 (1 row) 

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

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

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

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

لذلك ، سنقوم بتعيين autov Vacuum_freeze_max_age على مستوى الجدول (وفي نفس الوقت نرجع عامل الملء الطبيعي أيضًا). لسوء الحظ ، فإن الحد الأدنى للقيمة الممكنة هو 100000:

 => ALTER TABLE tfreeze SET (autovacuum_freeze_max_age = 100000, fillfactor = 100); 

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

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

 => CREATE PROCEDURE foo(id integer) AS $$ BEGIN INSERT INTO tfreeze VALUES (id, 'FOO'); COMMIT; END; $$ LANGUAGE plpgsql; => DO $$ BEGIN FOR i IN 101 .. 100100 LOOP CALL foo(i); END LOOP; END; $$; 

كما نرى ، تجاوز عمر آخر معاملة مجمدة في الجدول القيمة الدنيا:

 => SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze'; 
  relfrozenxid | age --------------+-------- 698 | 100006 (1 row) 

ولكن إذا انتظرت قليلاً الآن ، في سجل رسائل الخادم سيكون هناك إدخال حول الفراغ العدواني التلقائي للجدول "test.public.tfreeze" ، سيتغير رقم المعاملة المجمدة ، وسيعود عمرها إلى اللياقة:

 => SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze'; 
  relfrozenxid | age --------------+----- 100703 | 3 (1 row) 

هناك أيضًا شيء مثل تجميد المعاملات المتعددة ، لكننا لم نتحدث عنها بعد - سنؤجلها حتى نتحدث عن الأقفال حتى لا نتقدم نحن.

تجميد يدوي


في بعض الأحيان يكون من المناسب التحكم في التجميد يدويًا بدلاً من انتظار وصول التنظيف التلقائي.

يمكنك تجميد أمر يدويًا باستخدام أمر VACUUM FREEZE - سيتم تجميد جميع إصدارات الصف ، بغض النظر عن عمر المعاملات (كما لو كانت المعلمة autov Vacuum_freeze_min_age = 0). عند إعادة إنشاء جدول باستخدام أوامر VACUUM FULL أو CLUSTER ، يتم تجميد جميع الصفوف أيضًا.

لتجميد جميع قواعد البيانات ، يمكنك استخدام الأداة المساعدة:

 vacuumdb --all --freeze 

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

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

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

 | => BEGIN ISOLATION LEVEL REPEATABLE READ; | => SELECT txid_current(); 

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

 => BEGIN; => TRUNCATE tfreeze; => COPY tfreeze FROM stdin WITH FREEZE; 
 1 FOO 2 BAR 3 BAZ \. 
 => COMMIT; 

الآن ترى الصفقة الموازية بيانات جديدة ، على الرغم من أن هذا يكسر العزلة:

 | => SELECT count(*) FROM tfreeze; 
 | count | ------- | 3 | (1 row) 
 | => COMMIT; 

ولكن نظرًا لأنه من غير المحتمل أن يحدث هذا التحميل للبيانات بانتظام ، فعادةً ما لا تكون هذه مشكلة.

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

استنتاج


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

ابق معنا ، لتستمر!

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


All Articles