MVCC-3۔ سٹرنگ ورژن

لہذا، ہم نے متعلقہ مسائل پر غور کیا ہے موصلیت، اور کے بارے میں ایک اعتکاف کیا کم سطح پر ڈیٹا کو منظم کرنا. اور آخر کار ہم سب سے دلچسپ حصے پر پہنچ گئے - سٹرنگ ورژن۔

عنوان

جیسا کہ ہم پہلے ہی کہہ چکے ہیں، ہر قطار بیک وقت ڈیٹا بیس میں کئی ورژن میں موجود ہو سکتی ہے۔ ایک ورژن کسی نہ کسی طرح دوسرے سے ممتاز ہونا چاہیے۔ اس مقصد کے لیے، ہر ورژن میں دو نشانات ہوتے ہیں جو اس ورژن (xmin اور xmax) کے "وقت" کا تعین کرتے ہیں۔ اقتباسات میں - کیونکہ یہ وقت نہیں ہے جیسا کہ استعمال کیا جاتا ہے، لیکن ایک خاص بڑھتی ہوئی کاؤنٹر. اور یہ کاؤنٹر ٹرانزیکشن نمبر ہے۔

(ہمیشہ کی طرح، حقیقت زیادہ پیچیدہ ہے: کاؤنٹر کی محدود بٹ گنجائش کی وجہ سے ہر وقت لین دین کا نمبر نہیں بڑھ سکتا۔ لیکن جب ہم منجمد ہو جائیں گے تو ہم ان تفصیلات کو تفصیل سے دیکھیں گے۔)

جب ایک قطار بنتی ہے، xmin اس ٹرانزیکشن نمبر پر سیٹ کیا جاتا ہے جس نے INSERT کمانڈ جاری کیا تھا، اور xmax کو خالی چھوڑ دیا جاتا ہے۔

جب ایک قطار کو حذف کیا جاتا ہے، موجودہ ورژن کی xmax قدر کو اس لین دین کی تعداد کے ساتھ نشان زد کیا جاتا ہے جس نے DELETE کیا تھا۔

جب ایک قطار کو اپ ڈیٹ کمانڈ کے ذریعے تبدیل کیا جاتا ہے، تو اصل میں دو آپریشن کیے جاتے ہیں: DELETE اور INSERT۔ قطار کا موجودہ ورژن xmax کو اس ٹرانزیکشن کی تعداد کے برابر سیٹ کرتا ہے جس نے اپ ڈیٹ کیا تھا۔ پھر اسی سٹرنگ کا ایک نیا ورژن بنایا جاتا ہے۔ اس کی 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)

آئیے صفحہ کے مندرجات کو دیکھتے ہیں۔ pageinspect ایکسٹینشن کا 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

نوٹ کریں کہ 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 کے آخری صفحات RAM میں بفرز میں محفوظ ہیں، پھر بھی ہر بار 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 اشارہ بٹ کو قطار کے ورژن پر سیٹ کر دیا جائے گا۔ 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-tree میں میٹا ڈیٹا صفحہ اور "باقاعدہ" صفحات ہوتے ہیں۔

تاہم، صفحہ میں عام طور پر قطاروں اور قطاروں کی طرف اشارہ کرنے والوں کی ایک صف ہوتی ہے (بالکل ٹیبل کے صفحے کی طرح)۔ اس کے علاوہ، صفحہ کے آخر میں خصوصی ڈیٹا کے لیے جگہ ہے۔

انڈیکس کی قطاروں میں انڈیکس کی قسم کے لحاظ سے بہت مختلف ڈھانچے بھی ہوسکتے ہیں۔ مثال کے طور پر، B-tree کے لیے، پتی کے صفحات سے متعلق قطاروں میں انڈیکسنگ کلیدی قدر اور متعلقہ ٹیبل کی قطار کا حوالہ (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;

نیسٹڈ ٹرانزیکشنز

پوائنٹس محفوظ کریں۔

SQL میں بیان کیا گیا ہے۔ پوائنٹس کو محفوظ کریں (savepoint)، جو آپ کو کسی لین دین کے کچھ حصے کو بغیر کسی رکاوٹ کے اسے منسوخ کرنے کی اجازت دیتا ہے۔ لیکن یہ مندرجہ بالا خاکہ میں فٹ نہیں بیٹھتا، کیونکہ لین دین کی تمام تبدیلیوں کے لیے ایک ہی حیثیت ہے، اور جسمانی طور پر کوئی ڈیٹا واپس نہیں لیا جاتا ہے۔

اس فعالیت کو نافذ کرنے کے لیے، سیو پوائنٹ کے ساتھ لین دین کو کئی الگ الگ حصوں میں تقسیم کیا جاتا ہے۔ گھریلو لین دین (ذیلی لین دین)، جس کی حیثیت کو الگ سے منظم کیا جاسکتا ہے۔

نیسٹڈ ٹرانزیکشن کا اپنا نمبر ہوتا ہے (مرکزی لین دین کی تعداد سے زیادہ)۔ نیسٹڈ ٹرانزیکشنز کی سٹیٹس کو XACT میں معمول کے مطابق ریکارڈ کیا جاتا ہے، لیکن حتمی سٹیٹس کا انحصار مرکزی ٹرانزیکشن کی سٹیٹس پر ہوتا ہے: اگر اسے منسوخ کر دیا جاتا ہے، تو تمام نیسٹڈ ٹرانزیکشنز بھی منسوخ ہو جاتی ہیں۔

ٹرانزیکشن نیسٹنگ کے بارے میں معلومات PGDATA/pg_subtrans ڈائریکٹری میں فائلوں میں محفوظ کی جاتی ہیں۔ فائلوں تک مثال کی مشترکہ میموری میں بفرز کے ذریعے رسائی حاصل کی جاتی ہے، جس طرح XACT بفرز کی طرح ترتیب دی جاتی ہے۔

نیسٹڈ ٹرانزیکشنز کو خود مختار لین دین کے ساتھ الجھائیں نہیں۔ خود مختار لین دین کسی بھی طرح سے ایک دوسرے پر منحصر نہیں ہے، لیکن گھریلو لین دین کرتے ہیں۔ باقاعدہ 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() فنکشن مرکزی ٹرانزیکشن نمبر لوٹاتا ہے، نیسٹڈ ٹرانزیکشن نمبر نہیں۔

=> 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 میں واضح طور پر استعمال نہیں کیا جا سکتا، یعنی آپ موجودہ کو مکمل کیے بغیر نیا ٹرانزیکشن شروع نہیں کر سکتے۔ یہ طریقہ کار سیو پوائنٹس کا استعمال کرتے وقت، نیز 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

نیا تبصرہ شامل کریں