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 bitmap - ڏنل ورزن جي انهن ڪالمن کي نشانو بڻائي ٿو جن ۾ null ويل (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 صفحي جي چڪاس جي توسيع جو فنڪشن توهان کي پوائنٽرز ۽ قطار جي ورزن بابت معلومات حاصل ڪرڻ جي اجازت ڏئي ٿو:

=> 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

نوٽ ڪريو ته لفظ heap 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)

ساڳي طرح، پر خاص طور تي گهٽ تفصيلي، معلومات خود ٽيبل مان حاصل ڪري سگهجي ٿي، pseudo-columns 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 تائين رسائي نه هوندي.

ڇو نه اهي بٽ ٽرانزيڪشن طرفان مقرر ڪيا ويا آهن پاڻ داخل ڪري رهيا آهن؟ جڏهن هڪ داخل ٿئي ٿي، ٽرانزيڪشن اڃا تائين نه ڄاڻندو آهي ته اهو ڪامياب ٿيندو. ۽ ڪم ڪرڻ جي وقت، اهو هاڻي واضح ناهي ته ڪهڙيون لائينون ڪهڙيون صفحا تبديل ڪيون ويون آهن. اهڙا ڪيترائي صفحا ٿي سگهن ٿا، ۽ انهن کي ياد ڪرڻ غير منافع بخش آهي. ان کان سواء، ڪجهه صفحا شايد بفر ڪيش مان ڊسڪ کان خارج ٿي سگھن ٿيون؛ بٽس کي تبديل ڪرڻ لاءِ ان کي ٻيهر پڙهڻ سان ڪمٽمينٽ کي سستي ٿي ويندي.

بچت جو نقصان اهو آهي ته تبديلين کان پوء، ڪنهن به ٽرانزيڪشن (جيتوڻيڪ هڪ سادي پڙهڻ جي ڪارڪردگي - SELECT) بفر ڪيش ۾ ڊيٽا صفحن کي تبديل ڪرڻ شروع ڪري سگهي ٿو.

تنهن ڪري، اچو ته تبديلي کي درست ڪريو.

=> 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 اشارو بٽ قطار ورزن تي سيٽ ڪيو ويندو. ايڪس اينڪس نمبر پاڻ صفحي تي رهي ٿو، پر ڪو به ان تي نظر نه ڪندو.

=> 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 ٽرانزيڪشن ڏانهن. نمبر هڪ پروسيس ID ۽ هڪ ترتيب نمبر تي مشتمل آهي.

هن نمبر کي جاري ڪرڻ سڀني عملن جي وچ ۾ هم وقت سازي جي ضرورت ناهي ۽ تنهن ڪري تمام تيز آهي. اسان مجازي نمبر استعمال ڪرڻ جي هڪ ٻي سبب کان واقف ٿينداسين جڏهن اسان منجمد بابت ڳالهائينداسين.

ورچوئل نمبرن کي ڊيٽا سنيپ شاٽ ۾ ڪنهن به طريقي سان نه ورتو ويو آهي.

وقت جي مختلف نقطن تي، شايد سسٽم ۾ مجازي ٽرانزيڪشن ٿي سگهي ٿي انهن انگن سان جيڪي اڳ ۾ ئي استعمال ڪيا ويا آهن، ۽ اهو عام آهي. پر اهڙو انگ ڊيٽا صفحن ۾ نه ٿو لکي سگهجي، ڇاڪاڻ ته ايندڙ ڀيري صفحي تائين پهچندي اهو سڀ معنيٰ وڃائي ويهندو.

=> 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;

Nested ٽرانزيڪشن

پوائنٽون محفوظ ڪريو

SQL ۾ بيان ڪيل پوائنٽون محفوظ ڪريو (savepoint)، جيڪو توهان کي مڪمل طور تي مداخلت ڪرڻ جي بغير ڪنهن ٽرانزيڪشن جو حصو منسوخ ڪرڻ جي اجازت ڏئي ٿو. پر اهو مٿي ڏنل ڊراگرام ۾ نه ٿو اچي، ڇاڪاڻ ته ٽرانزيڪشن ان جي سڀني تبديلين لاء ساڳي حيثيت رکي ٿي، ۽ جسماني طور تي ڪابه ڊيٽا واپس نه ڪئي وئي آهي.

ھن ڪارڪردگي کي لاڳو ڪرڻ لاء، ھڪڙي ٽرانزيڪشن کي بچاء واري پوائنٽ سان ڪيترن ئي جدا جدا حصن ۾ ورهايو ويو آھي داخل ٿيل ٽرانزيڪشن (ذيلي ٽرانزيڪشن)، جنهن جي حيثيت الڳ الڳ منظم ڪري سگهجي ٿي.

Nested ٽرانزيڪشن جو پنهنجو نمبر هوندو آهي (مکيه ٽرانزيڪشن جي تعداد کان وڌيڪ). nested ٽرانزيڪشن جي حيثيت XACT ۾ معمولي طريقي سان رڪارڊ ڪئي وئي آهي، پر حتمي حيثيت بنيادي ٽرانزيڪشن جي حيثيت تي منحصر آهي: جيڪڏهن اهو منسوخ ڪيو ويو آهي، ته پوء سڀئي nested ٽرانزيڪشن پڻ منسوخ ٿي ويا آهن.

ٽرانزيڪشن nesting بابت معلومات PGDATA/pg_subtrans ڊاريڪٽري ۾ فائلن ۾ ذخيرو ٿيل آهي. فائلون بفرز ذريعي پهچون ٿيون مثال جي گڏيل ياداشت ۾، ساڳئي طرح منظم ڪيل XACT بفرز جي طور تي.

نسٽڊ ٽرانزيڪشن کي خودمختيار ٽرانزيڪشن سان گڏ نه ڪريو. خودمختيار ٽرانزيڪشن ڪنهن به طريقي سان هڪ ٻئي تي منحصر نه آهن، پر nested ٽرانزيڪشن ڪندا آهن. باقاعده PostgreSQL ۾ ڪي به خودمختيار ٽرانزيڪشن نه آهن، ۽، شايد، بهترين طور تي: انهن جي تمام گهڻي ضرورت هوندي آهي، تمام گهٽ، ۽ ٻين DBMSs ۾ انهن جي موجودگي بدسلوڪي کي ثابت ڪري ٿي، جنهن کان پوء هرڪو برداشت ڪري ٿو.

اچو ته ٽيبل کي صاف ڪريون، ٽرانزيڪشن شروع ڪريو ۽ قطار داخل ڪريو:

=> 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 sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

نوٽ ڪريو ته txid_current() فنڪشن واپسي مکيه ٽرانزيڪشن نمبر، نه nested ٽرانزيڪشن نمبر.

=> 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)

ھاڻي توھان واضح طور تي ڏسي سگھوٿا ته ھر ھڪ نسٽڊ ٽرانزيڪشن جي پنھنجي حيثيت آھي.

نوٽ ڪريو ته nested ٽرانزيڪشن SQL ۾ واضح طور تي استعمال نه ٿي ڪري سگھجي، اھو آھي، توھان موجوده ھڪڙي کي مڪمل ڪرڻ کان سواءِ نئون ٽرانزيڪشن شروع نٿا ڪري سگھو. هي ميکانيزم واضح طور تي چالو ڪيو ويندو آهي جڏهن محفوظ پوائنٽ استعمال ڪندي، ۽ انهي سان گڏ جڏهن 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

غلطيون ۽ عملن جي جوهر

ڇا ٿيندو جيڪڏهن ڪو آپريشن دوران غلطي ٿئي ٿي؟ مثال طور، هن طرح:

=> 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 اصل ۾ هر ڪمانڊ کان اڳ هڪ امڪاني بچاء واري پوائنٽ رکي ٿو، ۽ ناڪامي جي صورت ۾ ان کي رول بيڪ شروع ڪري ٿو. هي موڊ ڊفالٽ طور استعمال نه ڪيو ويو آهي، ڇاڪاڻ ته سيٽنگ پوائنٽون (جيتوڻيڪ انهن ڏانهن واپس وڃڻ کان سواء) اهم اوور هيڊ شامل آهي.

تسلسل.

جو ذريعو: www.habr.com

تبصرو شامل ڪريو