MVCC-3. إصدارات الصف

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

رأس


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

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

عند إنشاء السطر ، يتم تعيين xmin على رقم المعاملة التي نفذت أمر INSERT ، ولم يتم ملء xmax.

عند حذف صف ، يتم تمييز قيمة xmax الخاصة بالإصدار الحالي برقم المعاملة الذي أجرى DELETE.

عند تعديل سطر باستخدام الأمر UPDATE ، يتم تنفيذ عمليتين بالفعل: DELETE و INSERT. في الإصدار الحالي من السطر ، يتم تعيين xmax مساوًا لعدد المعاملة التي أجريت UPDATE. ثم يتم إنشاء نسخة جديدة من نفس الخط ؛ تتطابق قيمة xmin مع قيمة xmax الخاصة بالإصدار السابق.

يتم تضمين حقول xmin و xmax في رأس إصدار الصف. بالإضافة إلى هذه الحقول ، يحتوي الرأس على مجالات أخرى ، على سبيل المثال:

  • infomask - سلسلة من البتات التي تحدد خصائص هذا الإصدار. هناك الكثير منهم ؛ أهمها سوف ننظر تدريجيا.
  • ctid - رابط إلى الإصدار الأحدث ، التالي من نفس السطر. في الإصدار الأحدث ، الأحدث من السلسلة ، يشير ctid إلى هذا الإصدار نفسه. يحتوي الرقم على النموذج (x ، y) ، حيث يمثل x رقم الصفحة ، y هو الرقم التسلسلي للمؤشر في الصفيف.
  • صورة نقطية لقيم غير محددة - علامات تلك الأعمدة من هذا الإصدار التي تحتوي على قيمة غير محددة (NULL). لا تعد NULL واحدة من القيم المعتادة لأنواع البيانات ، لذلك يجب تخزين السمة بشكل منفصل.

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

إدراج


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

للتجارب ، أنشئ جدولًا جديدًا به عمودين وفهرس على أحدهما:

=> CREATE TABLE t( id serial, s text ); => CREATE INDEX ON t(s); 

أدخل سطر واحد ، بعد بدء المعاملة.

 => BEGIN; => INSERT INTO t(s) VALUES ('FOO'); 

هنا هو عدد المعاملات الحالية لدينا:

 => SELECT txid_current(); 
  txid_current -------------- 3664 (1 row) 

ألق نظرة على محتويات الصفحة. توفر الدالة heap_page_items الخاصة بملحق pageinspect معلومات حول المؤشرات وإصدارات الصف:

 => SELECT * FROM heap_page_items(get_raw_page('t',0)) \gx 
 -[ RECORD 1 ]------------------- lp | 1 lp_off | 8160 lp_flags | 1 lp_len | 32 t_xmin | 3664 t_xmax | 0 t_field3 | 0 t_ctid | (0,1) t_infomask2 | 2 t_infomask | 2050 t_hoff | 24 t_bits | t_oid | t_data | \x0100000009464f4f 

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

تعرض الوظيفة البيانات "كما هي" بتنسيق يصعب قراءته. لفهم ، سنترك جزءًا فقط من المعلومات ونفك تشفيرها:

 => SELECT '(0,'||lp||')' 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 as xmin, t_xmax as xmax, (t_infomask & 256) > 0 AS xmin_commited, (t_infomask & 512) > 0 AS xmin_aborted, (t_infomask & 1024) > 0 AS xmax_commited, (t_infomask & 2048) > 0 AS xmax_aborted, t_ctid FROM heap_page_items(get_raw_page('t',0)) \gx 
 -[ RECORD 1 ]-+------- ctid | (0,1) state | normal xmin | 3664 xmax | 0 xmin_commited | f xmin_aborted | f xmax_commited | f xmax_aborted | t t_ctid | (0,1) 

إليك ما فعلناه:

  • أضفنا صفرًا إلى رقم الفهرس لنجعله في نفس شكل t_ctid: (رقم الصفحة ، رقم الفهرس).
  • فك تشفير حالة مؤشر lp_flags. هنا "طبيعي" - وهذا يعني أن المؤشر يشير حقًا إلى إصدار السلسلة. سيتم النظر في القيم الأخرى في وقت لاحق.
  • من بين جميع أجزاء المعلومات ، تم حتى الآن تخصيص اثنين فقط من الأزواج. تشير البتتان xmin_committed و xmin_aborted إلى ما إذا كانت المعاملة برقم xmin ملتزمة (تم إلغاؤها). تشير بتتان متماثلتان إلى رقم المعاملة xmax.

ماذا نرى؟ عند إدراج صف في صفحة الجدول ، يظهر مؤشر بالرقم 1 ، يشير إلى الإصدار الأول والوحيد من الصف.

في إصدار السطر ، يتم ملء حقل xmin برقم المعاملة الحالية. لا تزال المعاملة نشطة ، لذلك لم يتم تعيين كلتا البتات xmin_committed و xmin_aborted.

يشير الحقل ctid الخاص بإصدار الصف إلى نفس الصف. هذا يعني أن الإصدار الأحدث غير موجود.

يتم ملء حقل xmax برقم وهمية 0 ، لأن هذا الإصدار من الخط لا يتم حذفه وهو ذو صلة. لن تهتم المعاملات بهذا الرقم ، لأنه تم تعيين بت xmax_aborted.

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

 => CREATE FUNCTION heap_page(relname text, pageno integer) RETURNS TABLE(ctid tid, state text, xmin text, 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) > 0 THEN ' (c)' WHEN (t_infomask & 512) > 0 THEN ' (a)' ELSE '' END AS xmin, t_xmax || CASE WHEN (t_infomask & 1024) > 0 THEN ' (c)' WHEN (t_infomask & 2048) > 0 THEN ' (a)' ELSE '' END AS xmax, t_ctid FROM heap_page_items(get_raw_page(relname,pageno)) ORDER BY lp; $$ LANGUAGE SQL; 

في هذا النموذج ، يكون من الواضح أكثر ما يحدث في رأس إصدار السلسلة:

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3664 | 0 (a) | (0,1) (1 row) 

يمكن الحصول على معلومات متشابهة ، لكن أقل تفصيلاً ، من الجدول نفسه ، باستخدام أعمدة الزائفة xmin و xmax:

 => SELECT xmin, xmax, * FROM t; 
  xmin | xmax | id | s ------+------+----+----- 3664 | 0 | 1 | FOO (1 row) 

تثبيت


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

XACT ليس جدول كتالوج نظام؛ هذه هي الملفات في دليل PGDATA / pg_xact. فيها ، لكل معاملة ، يتم تخصيص وحدتين: الالتزام والإجهاض - تمامًا كما هو الحال في رأس إصدار السطر. يتم تقسيم هذه المعلومات إلى عدة ملفات للراحة فقط ، وسنعود إلى هذه المشكلة عندما نفكر في التجميد. ويتم العمل مع هذه الملفات صفحة تلو الأخرى ، كما هو الحال مع جميع الملفات الأخرى.

لذلك ، عند ارتكاب معاملة في XACT ، يتم تعيين بت الالتزام لهذه المعاملة. وهذا كله يحدث أثناء الالتزام (على الرغم من أننا لا نتحدث عن المجلة التمهيدية حتى الآن).

عندما تصل أي معاملة أخرى إلى صفحة الجدول التي نظرنا إليها للتو ، فسيتعين عليها الإجابة على بعض الأسئلة.

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

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

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

لذلك ، إصلاح التغيير.

 => COMMIT; 

لم يتغير شيء في الصفحة (لكننا نعرف أن حالة المعاملة مسجلة بالفعل في XACT):

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3664 | 0 (a) | (0,1) (1 row) 

الآن سيتعين على المعاملة التي تصل إلى الصفحة أولاً تحديد حالة المعاملة xmin وكتابتها إلى وحدات بت المعلومات:

 => SELECT * FROM t; 
  id | s ----+----- 1 | FOO (1 row) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+-------+-------- (0,1) | normal | 3664 (c) | 0 (a) | (0,1) (1 row) 

إزالة


عند حذف أحد الخطوط ، يتم تسجيل رقم معاملة الحذف الحالية في حقل xmax من الإصدار الحالي ، وتتم إعادة تعيين بت xmax_aborted.

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

حذف الخط.

 => BEGIN; => DELETE FROM t; => SELECT txid_current(); 
  txid_current -------------- 3665 (1 row) 

نرى تسجيل رقم المعاملة في حقل xmax ، لكن لم يتم تعيين بتات المعلومات:

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+------+-------- (0,1) | normal | 3664 (c) | 3665 | (0,1) (1 row) 

إلغاء


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

 => ROLLBACK; => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+------+-------- (0,1) | normal | 3664 (c) | 3665 | (0,1) (1 row) 

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

 => SELECT * FROM t; 
  id | s ----+----- 1 | FOO (1 row) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+----------+-------- (0,1) | normal | 3664 (c) | 3665 (a) | (0,1) (1 row) 

تحديث


يعمل التحديث كما لو كان يحذف أولاً الإصدار الحالي من الصف ، ثم يدرج إصدارًا جديدًا.

 => BEGIN; => UPDATE t SET s = 'BAR'; => SELECT txid_current(); 
  txid_current -------------- 3666 (1 row) 

ينتج الطلب سطرًا واحدًا (إصدار جديد):

 => SELECT * FROM t; 
  id | s ----+----- 1 | BAR (1 row) 

ولكن في الصفحة نرى كلا الإصدارين:

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+-------+-------- (0,1) | normal | 3664 (c) | 3666 | (0,2) (0,2) | normal | 3666 | 0 (a) | (0,2) (2 rows) 

يتم تمييز الإصدار البعيد برقم المعاملة الحالي في حقل xmax. علاوة على ذلك ، تتم كتابة هذه القيمة على القيمة القديمة ، حيث تم إلغاء المعاملة السابقة. تتم إعادة تعيين بت xmax_aborted ، لأن حالة المعاملة الحالية لا تزال غير معروفة.

يشير الإصدار الأول من السطر الآن إلى الثاني (حقل t_ctid) ، كإصدار جديد.

يظهر مؤشر ثان وسطر ثان في صفحة الفهرس ، مرتبطين بالإصدار الثاني في صفحة الجدول.

كما هو الحال مع الحذف ، فإن قيمة xmax في الإصدار الأول من السلسلة هي علامة على أن السلسلة مقفلة.

حسنا ، أكمل الصفقة.

 => COMMIT; 

مؤشرات


حتى الآن ، تحدثنا فقط عن صفحات الجداول. وماذا يحدث داخل المؤشرات؟

تعتمد المعلومات الموجودة في صفحات الفهرس اعتمادًا كبيرًا على نوع الفهرس المحدد. وحتى نوع واحد من الفهرس به أنواع مختلفة من الصفحات. على سبيل المثال ، تحتوي الشجرة B على صفحة بها بيانات وصفحية وصفحات "عادية".

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

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

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

في الوقت نفسه ، في صفحة الفهرس ، نجد مؤشرات على كلا الإصدارين ، الحالي والقديم:

 => SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1); 
  itemoffset | ctid ------------+------- 1 | (0,2) 2 | (0,1) (2 rows) 

المعاملات الافتراضية


في الممارسة العملية ، يستخدم PostgreSQL تحسينات "لحفظ" أرقام المعاملات.

إذا كانت المعاملة تقوم بقراءة البيانات فقط ، فلن يؤثر ذلك على ظهور إصدارات الصف. لذلك ، في البداية ، تصدر عملية التقديم معاملة رقم ظاهري (xid ظاهري). يتكون الرقم من معرف العملية ورقم متسلسل.

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

لا يتم أخذ الأرقام الافتراضية في الاعتبار في لقطات البيانات.

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

 => BEGIN; => SELECT txid_current_if_assigned(); 
  txid_current_if_assigned -------------------------- (1 row) 

إذا بدأت المعاملة في تغيير البيانات ، يتم منحها رقم معاملة حقيقي وفريد.

 => UPDATE accounts SET amount = amount - 1.00; => SELECT txid_current_if_assigned(); 
  txid_current_if_assigned -------------------------- 3667 (1 row) 

 => COMMIT; 

المعاملات المتداخلة


حفظ النقاط


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

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

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

يتم تخزين المعلومات حول تداخل المعاملات في ملفات في دليل PGDATA / pg_subtrans. يتم الوصول إلى الملفات من خلال المخازن المؤقتة في الذاكرة المشتركة للمثيل ، ويتم تنظيمها بنفس الطريقة مثل المخازن المؤقتة XACT.

لا تخلط بين المعاملات المتداخلة والمعاملات المستقلة. المعاملات المستقلة لا تعتمد بأي حال على بعضها البعض ، والمعاملات المتداخلة تعتمد. لا توجد معاملات مستقلة في PostgreSQL المعتادة ، وربما للأفضل: في حالة الحاجة إليها ، في حالات نادرة جدًا ، ووجودها في قواعد بيانات إدارة قواعد البيانات (DBMS) الأخرى يثير إساءة ، والتي يعاني منها الجميع.

امسح الجدول ، وابدأ المعاملة وأدخل السطر:

 => TRUNCATE TABLE t; => BEGIN; => INSERT INTO t(s) VALUES ('FOO'); => SELECT txid_current(); 
  txid_current -------------- 3669 (1 row) 

 => SELECT xmin, xmax, * FROM t; 
  xmin | xmax | id | s ------+------+----+----- 3669 | 0 | 2 | FOO (1 row) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3669 | 0 (a) | (0,1) (1 row) 

الآن وضع savepoint وإدراج صف آخر.

 => SAVEPOINT sp; => INSERT INTO t(s) VALUES ('XYZ'); => SELECT txid_current(); 
  txid_current -------------- 3669 (1 row) 

لاحظ أن الدالة txid_current () تُرجع رقم المعاملة الرئيسية ، غير المتداخلة.

 => SELECT xmin, xmax, * FROM t; 
  xmin | xmax | id | s ------+------+----+----- 3669 | 0 | 2 | FOO 3670 | 0 | 3 | XYZ (2 rows) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+------+-------+-------- (0,1) | normal | 3669 | 0 (a) | (0,1) (0,2) | normal | 3670 | 0 (a) | (0,2) (2 rows) 

نعود إلى نقطة الحفظ ونضيف الصف الثالث.

 => ROLLBACK TO sp; => INSERT INTO t(s) VALUES ('BAR'); => SELECT xmin, xmax, * FROM t; 
  xmin | xmax | id | s ------+------+----+----- 3669 | 0 | 2 | FOO 3671 | 0 | 4 | BAR (2 rows) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+-------+-------- (0,1) | normal | 3669 | 0 (a) | (0,1) (0,2) | normal | 3670 (a) | 0 (a) | (0,2) (0,3) | normal | 3671 | 0 (a) | (0,3) (3 rows) 

في الصفحة ، نواصل مشاهدة الصف الذي تمت إضافته بواسطة المعاملة المتداخلة الملغاة.

نحن إصلاح التغييرات.

 => COMMIT; => SELECT xmin, xmax, * FROM t; 
  xmin | xmax | id | s ------+------+----+----- 3669 | 0 | 2 | FOO 3671 | 0 | 4 | BAR (2 rows) 

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+-------+-------- (0,1) | normal | 3669 (c) | 0 (a) | (0,1) (0,2) | normal | 3670 (a) | 0 (a) | (0,2) (0,3) | normal | 3671 (c) | 0 (a) | (0,3) (3 rows) 

الآن يمكنك أن ترى بوضوح أن كل معاملة متداخلة لها حالتها الخاصة.

لاحظ أنه لا يمكن استخدام المعاملات المتداخلة بشكل صريح في SQL ، أي أنه لا يمكنك بدء معاملة جديدة دون إكمال المعاملة الحالية. يتم استخدام هذه الآلية ضمنيًا عند استخدام savepoints ، وكذلك عند التعامل مع استثناءات PL / pgSQL وفي عدد من الحالات الأخرى الأكثر غرابة.

 => BEGIN; 
 BEGIN 
 => BEGIN; 
 WARNING: there is already a transaction in progress BEGIN 
 => COMMIT; 
 COMMIT 
 => COMMIT; 
 WARNING: there is no transaction in progress COMMIT 

أخطاء و atomicity العمليات


ماذا يحدث إذا حدث خطأ أثناء العملية؟ على سبيل المثال ، مثل هذا:

 => BEGIN; => SELECT * FROM t; 
  id | s ----+----- 2 | FOO 4 | BAR (2 rows) 

 => UPDATE t SET s = repeat('X', 1/(id-4)); 
 ERROR: division by zero 

لقد حدث خطأ. الآن تم اعتبار المعاملة مُجهضة ولا يُسمح بإجراء عملية واحدة فيها:

 => SELECT * FROM t; 
 ERROR: current transaction is aborted, commands ignored until end of transaction block 

وحتى إذا حاولت الالتزام بالتغييرات ، فسوف يقوم PostgreSQL بالإبلاغ عن الإلغاء:

 => COMMIT; 
 ROLLBACK 

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

 => SELECT * FROM heap_page('t',0); 
  ctid | state | xmin | xmax | t_ctid -------+--------+----------+-------+-------- (0,1) | normal | 3669 (c) | 3672 | (0,4) (0,2) | normal | 3670 (a) | 0 (a) | (0,2) (0,3) | normal | 3671 (c) | 0 (a) | (0,3) (0,4) | normal | 3672 | 0 (a) | (0,4) (4 rows) 

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

 => \set ON_ERROR_ROLLBACK on => BEGIN; => SELECT * FROM t; 
  id | s ----+----- 2 | FOO 4 | BAR (2 rows) 

 => UPDATE t SET s = repeat('X', 1/(id-4)); 
 ERROR: division by zero 

 => SELECT * FROM t; 
  id | s ----+----- 2 | FOO 4 | BAR (2 rows) 

 => COMMIT; 

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

أن تستمر.

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


All Articles