MVCC-3. Версияҳои сатр

Инак, мо масъалахои вобастаро дида баромадем изолятсия, ва дар бораи акибнишинй кард ташкили маълумот дар сатҳи паст. Ва ниҳоят мо ба қисми ҷолибтарин - версияҳои сатр расидем.

Сарлавҳа

Тавре ки мо аллакай гуфта будем, ҳар як сатр метавонад дар як вақт дар якчанд версияҳо дар базаи маълумот мавҷуд бошад. Як версияро аз дигараш ба навъе фарќ кардан лозим аст.Бо ин маќсад њар як версия ду аломат дорад, ки "ваќти" амали ин версияро муайян мекунанд (xmin ва xmax). Дар нохунакҳо - зеро он вақт нест, ки ин тавр истифода мешавад, балки ҳисобкунаки махсуси афзоишёбанда аст. Ва ин ҳисоб рақами транзаксия аст.

(Мавзӯи маъмул, воқеият мураккабтар аст: рақами транзаксия аз сабаби қобилияти маҳдуди ҳисобкунак наметавонад ҳама вақт зиёд шавад. Аммо мо ин тафсилотро ҳангоми яхкунӣ ба таври муфассал баррасӣ хоҳем кард.)

Вақте ки сатр сохта мешавад, xmin ба рақами транзаксия, ки фармони INSERT дода шудааст, гузошта мешавад ва xmax холӣ мемонад.

Вақте ки сатр нест карда мешавад, арзиши xmax-и версияи ҷорӣ бо рақами транзаксияе, ки DELETE-ро анҷом додааст, қайд карда мешавад.

Вақте ки сатр бо фармони UPDATE тағир дода мешавад, воқеан ду амал иҷро карда мешавад: DELETE ва INSERT. Варианти ҷории сатр xmax-ро ба рақами транзаксияе, ки НАВОРӢ анҷом додааст, муқаррар мекунад. Пас аз он версияи нави ҳамон сатр сохта мешавад; арзиши 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

Дар хотир доред, ки калимаи 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)

Маълумоти шабеҳ, вале ба таври назаррас камтар муфассалро аз худи ҷадвал бо истифода аз сутунҳои псевдо-сутунҳои xmin ва xmax гирифтан мумкин аст:

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

Таљњизот

Агар транзаксия бомуваффақият анҷом дода шавад, шумо бояд ҳолати онро дар хотир доред - қайд кунед, ки он содир шудааст. Барои ин сохторе бо номи XACT истифода мешавад (ва пеш аз версияи 10 он CLOG (commit log) ном дошт ва ин ном ҳоло ҳам дар ҷойҳои гуногун пайдо мешавад).

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 интизор шавад. Мо дар бораи бастани он дертар бештар сӯҳбат хоҳем кард. Дар айни замон, мо танҳо қайд мекунем, ки шумораи қулфҳои сатр номаҳдуд аст. Онҳо дар RAM ҷой намегиранд ва кори система аз шумораи онҳо зарар намебинад. Дуруст аст, ки муомилоти "дароз" камбудиҳои дигар доранд, аммо дар ин бора баъдтар.

Биёед сатрро нест кунем.

=> 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 виртуалӣ медиҳад. Рақам аз 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 муайян карда шудааст захира кардани нуқтаҳои (нуқтаи нигоҳдорӣ), ки ба шумо имкон медиҳад, ки як қисми транзаксияро бидуни қатъи он пурра бекор кунед. Аммо ин ба диаграммаи дар боло овардашуда мувофиқат намекунад, зеро транзаксия барои ҳама тағироташ як мақом дорад ва аз ҷиҳати ҷисмонӣ ягон маълумот баргардонида намешавад.

Барои татбиқи ин функсия, транзаксия бо нуқтаи захиракунӣ ба якчанд алоҳида тақсим карда мешавад муомилоти лона (субтрансакция), ки вазъи онро алохида идора кардан мумкин аст.

Амалиётҳои дохилӣ рақами худро доранд (аз шумораи транзаксияҳои асосӣ зиёдтар). Ҳолати транзаксияҳои дохилшуда ба таври муқаррарӣ дар 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 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 воқеан пеш аз ҳар як фармон нуқтаи нигоҳдории пинҳонӣ мегузорад ва дар сурати нокомӣ бозгашт ба он оғоз мекунад. Ин режим ба таври нобаёнӣ истифода намешавад, зеро муқаррар кардани нуқтаҳои захиравӣ (ҳатто бидуни бозгашт ба онҳо) хароҷоти зиёдро дар бар мегирад.

Идома.

Манбаъ: will.com

Илова Эзоҳ