MVCC-3. Mga bersyon ng string

Kaya, isinasaalang-alang namin ang mga isyung nauugnay sa pagkakabukod, at nag-retreat tungkol sa pag-aayos ng data sa mababang antas. At sa wakas nakarating kami sa pinakakawili-wiling bahagi - ang mga bersyon ng string.

Pamagat

Tulad ng nasabi na natin, ang bawat hilera ay maaaring sabay na umiral sa ilang mga bersyon sa database. Ang isang bersyon ay dapat na kahit papaano ay naiiba sa isa pa. Para sa layuning ito, ang bawat bersyon ay may dalawang marka na tumutukoy sa "oras" ng pagkilos ng bersyong ito (xmin at xmax). Sa mga quote - dahil ito ay hindi oras bilang tulad na ginagamit, ngunit isang espesyal na pagtaas ng counter. At ang counter na ito ay ang numero ng transaksyon.

(Gaya ng dati, ang katotohanan ay mas kumplikado: ang numero ng transaksyon ay hindi maaaring tumaas sa lahat ng oras dahil sa limitadong bit capacity ng counter. Ngunit titingnan natin nang detalyado ang mga detalyeng ito kapag nagyeyelo na tayo.)

Kapag nalikha ang isang hilera, itatakda ang xmin sa numero ng transaksyon na naglabas ng utos na INSERT, at ang xmax ay naiwang blangko.

Kapag ang isang row ay tinanggal, ang xmax na halaga ng kasalukuyang bersyon ay minarkahan ng bilang ng transaksyon na nagsagawa ng DELETE.

Kapag ang isang hilera ay binago ng isang UPDATE na utos, dalawang operasyon ang aktwal na isinasagawa: DELETE at INSERT. Ang kasalukuyang bersyon ng row ay nagtatakda ng xmax na katumbas ng bilang ng transaksyon na nagsagawa ng UPDATE. Ang isang bagong bersyon ng parehong string ay ginawa; ang xmin value nito ay tumutugma sa xmax value ng nakaraang bersyon.

Ang xmin at xmax field ay kasama sa row version header. Bilang karagdagan sa mga field na ito, naglalaman ang header ng iba, halimbawa:

  • Ang infomask ay isang serye ng mga piraso na tumutukoy sa mga katangian ng bersyong ito. Mayroong medyo marami sa kanila; Unti-unti nating isasaalang-alang ang mga pangunahing.
  • Ang ctid ay isang link sa susunod, mas bagong bersyon ng parehong linya. Para sa pinakabago, pinakakasalukuyang bersyon ng isang linya, ang ctid ay tumutukoy sa bersyong ito mismo. Ang numero ay may anyo (x,y), kung saan ang x ay ang numero ng pahina, y ang index number sa array.
  • null bitmap - Minamarkahan ang mga column ng isang ibinigay na bersyon na naglalaman ng null value (NULL). Ang NULL ay hindi isa sa mga normal na halaga ng uri ng data, kaya ang katangian ay dapat na naka-imbak nang hiwalay.

Bilang resulta, ang header ay medyo malaki - hindi bababa sa 23 byte para sa bawat bersyon ng linya, at kadalasang higit pa dahil sa NULL bitmap. Kung ang talahanayan ay "makitid" (iyon ay, naglalaman ng ilang mga column), ang overhead ay maaaring tumagal ng higit pa kaysa sa kapaki-pakinabang na impormasyon.

magpasok

Tingnan natin nang maigi kung paano isinasagawa ang mga operasyon ng string na may mababang antas, simula sa pagpasok.

Para sa mga eksperimento, gumawa tayo ng bagong talahanayan na may dalawang column at isang index sa isa sa mga ito:

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

Maglagay tayo ng isang row pagkatapos magsimula ng transaksyon.

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

Narito ang aming kasalukuyang numero ng transaksyon:

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

Tingnan natin ang mga nilalaman ng pahina. Ang heap_page_items function ng pageinspect extension ay nagbibigay-daan sa iyong makakuha ng impormasyon tungkol sa mga pointer at row na bersyon:

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

Tandaan na ang salitang heap sa PostgreSQL ay tumutukoy sa mga talahanayan. Ito ay isa pang kakaibang gamit ng termino - kilala ang isang tambak istraktura ng data, na walang pagkakatulad sa talahanayan. Dito ang salita ay ginagamit sa kahulugan ng "lahat ay itinapon nang sama-sama," bilang kabaligtaran sa iniutos na mga index.

Ang function ay nagpapakita ng data "as is", sa isang format na mahirap maunawaan. Upang malaman ito, mag-iiwan lamang kami ng bahagi ng impormasyon at intindihin ito:

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

Narito ang ginawa namin:

  • Nagdagdag ng zero sa index number para maging katulad ito ng t_ctid: (page number, index number).
  • Na-decipher ang estado ng lp_flags pointer. Narito ito ay "normal" - nangangahulugan ito na ang pointer ay aktwal na tumutukoy sa bersyon ng string. Titingnan natin ang iba pang mga kahulugan mamaya.
  • Sa lahat ng mga piraso ng impormasyon, dalawang pares lamang ang natukoy sa ngayon. Ang xmin_committed at xmin_aborted bits ay nagpapahiwatig kung ang numero ng transaksyon na xmin ay ginawa (na-abort). Dalawang magkatulad na bit ang tumutukoy sa numero ng transaksyon na xmax.

Ano ang nakikita natin? Kapag nagpasok ka ng isang row, isang index number 1 ang lalabas sa page ng talahanayan, na tumuturo sa una at tanging bersyon ng row.

Sa string na bersyon, ang xmin field ay puno ng kasalukuyang numero ng transaksyon. Aktibo pa rin ang transaksyon, kaya hindi nakatakda ang parehong xmin_committed at xmin_aborted bits.

Ang row version ctid field ay tumutukoy sa parehong row. Nangangahulugan ito na walang mas bagong bersyon.

Ang patlang ng xmax ay puno ng isang dummy na numero 0 dahil ang bersyon na ito ng row ay hindi natanggal at kasalukuyang. Hindi bibigyan ng pansin ng mga transaksyon ang numerong ito dahil nakatakda ang xmax_aborted bit.

Gumawa tayo ng isa pang hakbang patungo sa pagpapabuti ng pagiging madaling mabasa sa pamamagitan ng pagdaragdag ng mga piraso ng impormasyon sa mga numero ng transaksyon. At gumawa tayo ng function, dahil kakailanganin natin ang kahilingan nang higit sa isang beses:

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

Sa form na ito, mas malinaw kung ano ang nangyayari sa header ng bersyon ng row:

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

Katulad, ngunit hindi gaanong detalyado, ang impormasyon ay maaaring makuha mula sa talahanayan mismo, gamit ang pseudo-columns xmin at xmax:

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

Pag-aayos

Kung matagumpay na nakumpleto ang isang transaksyon, kailangan mong tandaan ang katayuan nito - tandaan na ito ay nakatuon. Upang gawin ito, ginagamit ang isang istraktura na tinatawag na XACT (at bago ang bersyon 10 ito ay tinawag na CLOG (commit log) at ang pangalang ito ay matatagpuan pa rin sa iba't ibang lugar).

Ang XACT ay hindi isang system catalog table; ito ang mga file sa PGDATA/pg_xact na direktoryo. Mayroon silang dalawang bit para sa bawat transaksyon: nakatuon at na-abort - tulad ng sa header ng bersyon ng row. Ang impormasyong ito ay nahahati sa ilang mga file para lamang sa kaginhawahan; babalik kami sa isyung ito kapag isinasaalang-alang namin ang pagyeyelo. At ang pagtatrabaho sa mga file na ito ay isinasagawa sa bawat pahina, tulad ng sa lahat ng iba pa.

Kaya, kapag ang isang transaksyon ay ginawa sa XACT, ang nakatuon na bit ay nakatakda para sa transaksyong ito. At ito lang ang nangyayari habang nagko-commit (bagaman hindi pa natin pinag-uusapan ang pre-recording log).

Kapag na-access ng isa pang transaksyon ang page ng talahanayan na kakatingin lang namin, kakailanganin nitong sagutin ang ilang tanong.

  1. Nakumpleto na ba ang transaksyon ng xmin? Kung hindi, hindi dapat makita ang ginawang bersyon ng string.
    Ginagawa ang pagsusuring ito sa pamamagitan ng pagtingin sa isa pang istraktura, na matatagpuan sa nakabahaging memorya ng instance at tinatawag na ProcArray. Naglalaman ito ng isang listahan ng lahat ng mga aktibong proseso, at para sa bawat isa ang bilang ng kasalukuyang (aktibo) na transaksyon nito ay ipinahiwatig.
  2. Kung nakumpleto, kung gayon paano - sa pamamagitan ng pag-commit o pagkansela? Kung kinansela, hindi rin dapat makita ang bersyon ng row.
    Ito mismo ang para sa XACT. Ngunit, kahit na ang mga huling pahina ng XACT ay naka-imbak sa mga buffer sa RAM, mahal pa rin na suriin ang XACT sa bawat oras. Samakatuwid, kapag natukoy na ang katayuan ng transaksyon, isusulat ito sa xmin_committed at xmin_aborted na mga bit ng string na bersyon. Kung ang isa sa mga bit na ito ay nakatakda, ang estado ng transaksyon xmin ay itinuturing na kilala at ang susunod na transaksyon ay hindi na kailangang ma-access ang XACT.

Bakit ang mga bit na ito ay hindi itinakda ng mismong transaksyon na gumagawa ng insert? Kapag may naganap na insert, hindi pa alam ng transaksyon kung magtatagumpay ito. At sa sandali ng paggawa, hindi na malinaw kung aling mga linya kung saan ang mga pahina ay binago. Maaaring mayroong maraming ganoong mga pahina, at ang pagsasaulo sa mga ito ay hindi kapaki-pakinabang. Sa karagdagan, ang ilang mga pahina ay maaaring evicted mula sa buffer cache sa disk; ang pagbabasa muli ng mga ito upang baguhin ang mga bit ay makabuluhang magpapabagal sa commit.

Ang downside ng pagtitipid ay na pagkatapos ng mga pagbabago, anumang transaksyon (kahit isa na nagsasagawa ng simpleng pagbabasa - SELECT) ay maaaring magsimulang baguhin ang mga pahina ng data sa buffer cache.

Kaya, ayusin natin ang pagbabago.

=> COMMIT;

Walang nagbago sa page (ngunit alam namin na ang katayuan ng transaksyon ay naitala na sa XACT):

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

Ngayon ang transaksyon na unang nag-access sa pahina ay kailangang matukoy ang katayuan ng transaksyon ng xmin at isulat ito sa mga piraso ng impormasyon:

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

Pag-aalis

Kapag ang isang row ay tinanggal, ang numero ng kasalukuyang pagtanggal ng transaksyon ay isusulat sa xmax field ng kasalukuyang bersyon, at ang xmax_aborted bit ay iki-clear.

Tandaan na ang nakatakdang halaga ng xmax na naaayon sa aktibong transaksyon ay nagsisilbing row lock. Kung nais ng isa pang transaksyon na i-update o tanggalin ang row na ito, mapipilitan itong maghintay para makumpleto ang transaksyon xmax. Pag-uusapan pa natin ang tungkol sa pagharang mamaya. Sa ngayon, tandaan lang namin na ang bilang ng mga lock ng row ay walang limitasyon. Hindi sila kumukuha ng espasyo sa RAM at ang pagganap ng system ay hindi nagdurusa sa kanilang numero. Totoo, ang mga "mahabang" mga transaksyon ay may iba pang mga disadvantages, ngunit higit pa doon sa ibang pagkakataon.

Tanggalin natin ang linya.

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

Nakikita namin na ang numero ng transaksyon ay nakasulat sa xmax field, ngunit ang mga bit ng impormasyon ay hindi nakatakda:

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

Pawalang-bisa

Ang pag-abort ng mga pagbabago ay gumagana katulad ng pag-commit, tanging sa XACT lang ang na-abort na bit ay nakatakda para sa transaksyon. Ang pag-undo ay kasing bilis ng paggawa. Bagama't ang command ay tinatawag na ROLLBACK, ang mga pagbabago ay hindi ibinabalik: lahat ng bagay na pinamamahalaang baguhin ng transaksyon sa mga pahina ng data ay nananatiling hindi nagbabago.

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

Kapag na-access ang page, susuriin ang status at ang xmax_aborted hint bit ay itatakda sa bersyon ng row. Ang xmax number mismo ay nananatili sa pahina, ngunit walang titingin dito.

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

I-update ang

Gumagana ang pag-update na parang tinanggal nito ang kasalukuyang bersyon ng row at pagkatapos ay nagpasok ng bago.

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

Ang query ay gumagawa ng isang linya (bagong bersyon):

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

Ngunit sa pahina ay makikita natin ang parehong mga bersyon:

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

Ang tinanggal na bersyon ay minarkahan ng kasalukuyang numero ng transaksyon sa xmax field. Bukod dito, ang halagang ito ay isinulat sa luma, dahil nakansela ang nakaraang transaksyon. At ang xmax_aborted bit ay na-clear dahil ang status ng kasalukuyang transaksyon ay hindi pa alam.

Ang unang bersyon ng linya ngayon ay tumutukoy sa pangalawa (t_ctid field) bilang ang mas bago.

Lumilitaw ang pangalawang index sa pahina ng index at ang pangalawang hilera ay tumutukoy sa pangalawang bersyon sa pahina ng talahanayan.

Tulad ng pagtanggal, ang halaga ng xmax sa unang bersyon ng row ay isang indikasyon na naka-lock ang row.

Well, kumpletuhin natin ang transaksyon.

=> COMMIT;

Mga Index

Sa ngayon ay napag-usapan lamang namin ang tungkol sa mga pahina ng talahanayan. Ano ang nangyayari sa loob ng mga index?

Ang impormasyon sa mga pahina ng index ay lubhang nag-iiba depende sa partikular na uri ng index. At kahit isang uri ng index ay may iba't ibang uri ng mga pahina. Halimbawa, ang B-tree ay may metadata page at β€œregular” na page.

Gayunpaman, ang pahina ay karaniwang may hanay ng mga pointer sa mga row at sa mga row mismo (tulad ng isang page ng talahanayan). Bilang karagdagan, sa dulo ng pahina ay may puwang para sa espesyal na data.

Ang mga hilera sa mga index ay maaari ding magkaroon ng ibang mga istraktura depende sa uri ng index. Halimbawa, para sa isang B-tree, ang mga row na nauugnay sa mga leaf page ay naglalaman ng indexing key value at isang reference (ctid) sa kaukulang hilera ng talahanayan. Sa pangkalahatan, ang index ay maaaring isaayos sa isang ganap na naiibang paraan.

Ang pinakamahalagang punto ay walang mga bersyon ng row sa mga index ng anumang uri. Well, o maaari nating ipagpalagay na ang bawat linya ay kinakatawan ng eksaktong isang bersyon. Sa madaling salita, walang xmin at xmax na mga patlang sa index row header. Maaari naming ipagpalagay na ang mga link mula sa index ay humahantong sa lahat ng mga bersyon ng talahanayan ng mga hilera - upang malaman mo kung aling bersyon ang makikita lamang ng transaksyon sa pamamagitan ng pagtingin sa talahanayan. (Gaya ng nakasanayan, hindi ito ang buong katotohanan. Sa ilang mga kaso, maaaring i-optimize ng visibility map ang proseso, ngunit titingnan natin ito nang mas detalyado sa ibang pagkakataon.)

Kasabay nito, sa pahina ng index ay nakakahanap kami ng mga pointer sa parehong mga bersyon, pareho ang kasalukuyang isa at ang luma:

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

Mga virtual na transaksyon

Sa pagsasagawa, ang PostgreSQL ay gumagamit ng mga pag-optimize na nagbibigay-daan dito na "i-save" ang mga numero ng transaksyon.

Kung nagbabasa lang ng data ang isang transaksyon, wala itong epekto sa visibility ng mga bersyon ng row. Samakatuwid, ang proseso ng serbisyo ay nag-isyu muna ng isang virtual xid sa transaksyon. Ang numero ay binubuo ng isang process ID at isang sequence number.

Ang pagbibigay ng numerong ito ay hindi nangangailangan ng pag-synchronize sa pagitan ng lahat ng mga proseso at samakatuwid ay napakabilis. Makikilala natin ang isa pang dahilan ng paggamit ng mga virtual na numero kapag pinag-uusapan natin ang tungkol sa pagyeyelo.

Ang mga virtual na numero ay hindi isinasaalang-alang sa anumang paraan sa mga snapshot ng data.

Sa iba't ibang mga punto sa oras, maaaring mayroong mga virtual na transaksyon sa system na may mga numero na nagamit na, at ito ay normal. Ngunit ang naturang numero ay hindi maaaring isulat sa mga pahina ng data, dahil sa susunod na pag-access sa pahina ay maaaring mawala ang lahat ng kahulugan nito.

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

Kung ang isang transaksyon ay nagsimulang magbago ng data, ito ay bibigyan ng isang tunay, natatanging numero ng transaksyon.

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

=> COMMIT;

Mga Nested na Transaksyon

I-save ang mga puntos

Tinukoy sa SQL makatipid ng mga puntos (savepoint), na nagbibigay-daan sa iyong kanselahin ang bahagi ng isang transaksyon nang hindi ito ganap na naaabala. Ngunit hindi ito umaangkop sa diagram sa itaas, dahil ang transaksyon ay may parehong katayuan para sa lahat ng mga pagbabago nito, at pisikal na walang data na ibinabalik.

Para ipatupad ang functionality na ito, ang isang transaksyon na may savepoint ay nahahati sa ilang hiwalay mga nested na transaksyon (subtransaction), ang katayuan nito ay maaaring pamahalaan nang hiwalay.

Ang mga nested na transaksyon ay may sariling numero (mas mataas kaysa sa bilang ng pangunahing transaksyon). Ang katayuan ng mga nested na transaksyon ay naitala sa karaniwang paraan sa XACT, ngunit ang huling katayuan ay nakasalalay sa katayuan ng pangunahing transaksyon: kung ito ay kinansela, ang lahat ng mga nested na transaksyon ay kakanselahin din.

Ang impormasyon tungkol sa transaction nesting ay naka-store sa mga file sa PGDATA/pg_subtrans directory. Naa-access ang mga file sa pamamagitan ng mga buffer sa nakabahaging memorya ng instance, na nakaayos sa parehong paraan tulad ng mga buffer ng XACT.

Huwag ipagkamali ang mga nested na transaksyon sa mga autonomous na transaksyon. Ang mga autonomous na transaksyon ay hindi umaasa sa isa't isa sa anumang paraan, ngunit ang mga nested na transaksyon. Walang mga autonomous na transaksyon sa regular na PostgreSQL, at, marahil, para sa pinakamahusay: kinakailangan ang mga ito, napakabihirang, at ang kanilang presensya sa iba pang mga DBMS ay nagbubunsod ng pang-aabuso, kung saan ang lahat ay nagdurusa.

I-clear natin ang talahanayan, magsimula ng transaksyon at ipasok ang row:

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

Ngayon maglagay tayo ng save point at magpasok ng isa pang linya.

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

Tandaan na ibinabalik ng function na nxid_current() ang pangunahing numero ng transaksyon, hindi ang nested na numero ng transaksyon.

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

Bumalik tayo sa save point at ipasok ang ikatlong linya.

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

Sa page, patuloy naming nakikita ang row na idinagdag ng kinanselang nested na transaksyon.

Inaayos namin ang mga pagbabago.

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

Ngayon ay malinaw mong makikita na ang bawat nested na transaksyon ay may sariling katayuan.

Tandaan na ang mga nested na transaksyon ay hindi maaaring gamitin nang tahasan sa SQL, ibig sabihin, hindi ka makakapagsimula ng bagong transaksyon nang hindi kinukumpleto ang kasalukuyang transaksyon. Ang mekanismong ito ay implicitly na isinaaktibo kapag gumagamit ng mga savepoint, gayundin kapag pinangangasiwaan ang PL/pgSQL exception at sa ilang iba pang mas kakaibang mga kaso.

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

Mga error at atomicity ng mga operasyon

Ano ang mangyayari kung may naganap na error habang nagsasagawa ng operasyon? Halimbawa, tulad nito:

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

May nangyaring pagakamali. Ngayon ang transaksyon ay itinuturing na na-abort at walang mga operasyon na pinapayagan dito:

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

At kahit na subukan mong gawin ang mga pagbabago, ang PostgreSQL ay mag-uulat ng isang abort:

=> COMMIT;
ROLLBACK

Bakit hindi maaaring magpatuloy ang isang transaksyon pagkatapos ng pagkabigo? Ang katotohanan ay ang isang error ay maaaring lumitaw sa paraang makakakuha tayo ng access sa bahagi ng mga pagbabago - ang atomicity ng hindi kahit na ang transaksyon, ngunit ang operator ay malalabag. Tulad ng sa aming halimbawa, kung saan ang operator ay nakapag-update ng isang linya bago ang error:

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

Dapat sabihin na ang psql ay may isang mode na nagpapahintulot pa rin sa transaksyon na magpatuloy pagkatapos ng isang pagkabigo na parang ang mga aksyon ng maling operator ay pinabalik.

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

Hindi mahirap hulaan na sa mode na ito, ang psql ay talagang naglalagay ng implicit save point bago ang bawat command, at kung sakaling mabigo ay magsisimula ng rollback dito. Ang mode na ito ay hindi ginagamit bilang default, dahil ang pagtatakda ng mga savepoint (kahit na hindi binabalikan ang mga ito) ay nagsasangkot ng makabuluhang overhead.

pagpapatuloy.

Pinagmulan: www.habr.com

Magdagdag ng komento