MVCC-3. Жолдық нұсқалар

Сонымен, біз байланысты мәселелерді қарастырдық оқшаулау, және шамамен шегініс жасады деректерді төмен деңгейде ұйымдастыру. Соңында біз ең қызықты бөлікке - жолдық нұсқаларға келдік.

тақырып

Жоғарыда айтқанымыздай, әрбір жол бір уақытта дерекқорда бірнеше нұсқада болуы мүмкін. Бір нұсқаны екіншісінен қандай да бір түрде ажырату керек.Осы мақсатта әрбір нұсқада осы нұсқаның әрекет ету «уақытын» анықтайтын екі белгі бар (xmin және xmax). Тырнақшаларда - өйткені ол уақыт сияқты емес, арнайы өсетін есептегіш қолданылады. Және бұл есептегіш транзакция нөмірі болып табылады.

(Әдеттегідей, шындық күрделірек: есептегіштің шектеулі биттік сыйымдылығына байланысты транзакция нөмірі үнемі көбейе алмайды. Бірақ біз қатып қалған кезде бұл мәліметтерді егжей-тегжейлі қарастырамыз.)

Жол жасалғанда, xmin INSERT пәрменін шығарған транзакция нөміріне орнатылады және xmax бос қалдырылады.

Жол жойылған кезде, ағымдағы нұсқаның xmax мәні ЖОЮ әрекетін орындаған транзакция нөмірімен белгіленеді.

Жол ЖАҢАЛЫҚТАУ пәрменімен өзгертілгенде, шын мәнінде екі әрекет орындалады: DELETE және INSERT. Жолдың ағымдағы нұсқасы UPDATE орындаған транзакция санына тең 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)

Беттің мазмұнын қарастырайық. 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 (commit log) деп аталды және бұл атау әлі де әртүрлі жерлерде кездеседі).

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 тілінде анықталған ұпайларды үнемдеңіз (сақтау нүктесі), ол транзакцияның бір бөлігін толығымен үзбей тоқтатуға мүмкіндік береді. Бірақ бұл жоғарыдағы диаграммаға сәйкес келмейді, өйткені транзакция барлық өзгерістер үшін бірдей күйге ие және физикалық түрде ешқандай деректер кері қайтарылмайды.

Бұл функцияны іске асыру үшін сақтау нүктесі бар транзакция бірнеше бөлек бөліктерге бөлінеді кірістірілген транзакциялар (қосалқы транзакция), оның мәртебесін бөлек басқаруға болады.

Кірістірілген транзакциялардың өз нөмірі бар (негізгі транзакция санынан жоғары). Кірістірілген транзакциялардың күйі XACT-та әдеттегі жолмен жазылады, бірақ соңғы күй негізгі транзакцияның күйіне байланысты: егер ол жойылса, барлық кірістірілген транзакциялар да жойылады.

Транзакцияны енгізу туралы ақпарат PGDATA/pg_subtrans каталогындағы файлдарда сақталады. Файлдарға XACT буферлері сияқты ұйымдастырылған дананың ортақ жадындағы буферлер арқылы қол жеткізіледі.

Кірістірілген транзакцияларды автономды транзакциялармен шатастырмаңыз. Автономды транзакциялар бір-біріне ешқандай тәуелді емес, бірақ кірістірілген транзакциялар. Кәдімгі PostgreSQL-де автономды транзакциялар жоқ және, мүмкін, ең жақсысы үшін: олар өте сирек қажет және олардың басқа ДҚБЖ-да болуы теріс пайдалануды тудырады, содан кейін бәрі зардап шегеді.

Кестені тазалап, транзакцияны бастаймыз және жолды енгіземіз:

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

пікір қалдыру