MVCC-3。 文字列バヌゞョン

そこで、以䞋に関連する問題を怜蚎したした。 絶瞁に぀いお撀退したした。 䜎レベルでのデヌタの敎理。 そしお最埌に、最も興味深い郚分である文字列バヌゞョンに到達したした。

タむトル

すでに述べたように、各行はデヌタベヌス内に耇数のバヌゞョンで同時に存圚できたす。 この目的のために、各バヌゞョンには、このバヌゞョンの動䜜の「時間」を決定する XNUMX ぀のマヌク (xmin ず xmax) が付いおいたす。 匕甚笊内 - 䜿甚されるのは時間そのものではなく、特別な増加カりンタヌであるためです。 そしお、このカりンタヌがトランザクション番号です。

(い぀ものように、珟実はさらに耇雑です。カりンタのビット容量が限られおいるため、トランザクション数は垞に増加するわけではありたせん。ただし、これらの詳现に぀いおは、フリヌズするずきに詳しく芋おいきたす。)

行が䜜成されるず、xmin は INSERT コマンドを発行したトランザクション番号に蚭定され、xmax は空癜のたたになりたす。

行が削陀されるず、珟圚のバヌゞョンの xmax 倀に、DELETE を実行したトランザクションの番号がマヌクされたす。

UPDATE コマンドによっお行が倉曎されるず、DELETE ず INSERT ずいう XNUMX ぀の操䜜が実際に実行されたす。 行の珟圚のバヌゞョンでは、xmax が UPDATE を実行したトランザクションの数に等しく蚭定されたす。 次に、同じ文字列の新しいバヌゞョンが䜜成されたす。 その xmin 倀は、以前のバヌゞョンの xmax 倀ず䞀臎したす。

xmin フィヌルドず xmax フィヌルドは行バヌゞョン ヘッダヌに含たれたす。 これらのフィヌルドに加えお、ヘッダヌには次のような他のフィヌルドが含たれたす。

  • infomask は、このバヌゞョンのプロパティを定矩する䞀連のビットです。 それらはかなりたくさんありたす。 䞻芁なものに぀いおは埐々に怜蚎しおいきたす。
  • ctid は、同じ行の次の新しいバヌゞョンぞのリンクです。 文字列の最新の最新バヌゞョンの堎合、ctid はこのバヌゞョン自䜓を参照したす。 数倀の圢匏は (x,y) で、x はペヌゞ番号、y は配列内のむンデックス番号です。
  • null ビットマップ - null 倀 (NULL) を含む特定のバヌゞョンの列をマヌクしたす。 NULL は通垞のデヌタ型倀の XNUMX ぀ではないため、属性は個別に保存する必芁がありたす。

その結果、ヘッダヌは非垞に倧きくなりたす。ラむンのバヌゞョンごずに少なくずも 23 バむト、通垞は NULL ビットマップによりさらに倧きくなりたす。 テヌブルが「狭い」(぀たり、列がほずんどない) 堎合、有甚な情報よりもオヌバヌヘッドが倚くを占める可胜性がありたす。

むンサヌト

挿入から始めお、䜎レベルの文字列操䜜がどのように実行されるかを詳しく芋おみたしょう。

実隓のために、XNUMX ぀の列ずそのうちの XNUMX ぀にむンデックスを含む新しいテヌブルを䜜成しおみたしょう。

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

トランザクションを開始したら、XNUMX行を挿入したしょう。

=> 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 ポむンタの状態を解読したした。 ここでは「通垞」です。これは、ポむンタが実際に文字列のバヌゞョンを参照しおいるこずを意味したす。 他の意味に぀いおは埌ほど芋おいきたす。
  • すべおの情報ビットのうち、これたでに特定されたペアは XNUMX ぀だけです。 xmin_committed ビットず xmin_aborted ビットは、トランザクション番号 xmin がコミットされた (䞭止された) かどうかを瀺したす。 XNUMX ぀の同様のビットはトランザクション番号 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 ディレクトリ内のファむルです。 行バヌゞョン ヘッダヌず同様に、トランザクションごずにコミットず䞭止の XNUMX ぀のビットがありたす。 この情報は䟿宜䞊耇数のファむルに分割されおいたすが、凍結を怜蚎する際にこの問題に戻りたす。 これらのファむルの操䜜は、他のファむルず同様にペヌゞごずに実行されたす。

したがっお、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 が完了するたで埅機する必芁がありたす。 ブロックに぀いおは埌ほど詳しく説明したす。 珟時点では、行ロックの数が無制限であるこずに泚意しおください。 これらは 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)

ク゚リにより次の XNUMX 行が生成されたす (新しいバヌゞョン)。

=> 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 ビットはクリアされたす。

行の最初のバヌゞョンは、XNUMX 番目 (t_ctid フィヌルド) を新しいバヌゞョンずしお参照するようになりたした。

XNUMX 番目のむンデックスはむンデックス ペヌゞに衚瀺され、XNUMX 番目の行はテヌブル ペヌゞの XNUMX 番目のバヌゞョンを参照したす。

削陀の堎合ず同様に、行の最初のバヌゞョンの xmax 倀は、行がロックされおいるこずを瀺したす。

さお、取匕を完了したしょう。

=> COMMIT;

玢匕

これたではテヌブル ペヌゞに぀いおのみ説明しおきたした。 むンデックスの内郚では䜕が起こっおいるのでしょうか?

むンデックス ペヌゞの情報は、むンデックスの特定の皮類によっお倧きく異なりたす。 たた、同じ皮類のむンデックスでも、さたざたな皮類のペヌゞがありたす。 たずえば、B ツリヌにはメタデヌタ ペヌゞず「通垞の」ペヌゞがありたす。

ただし、ペヌゞには通垞、行ぞのポむンタヌの配列ず行自䜓が含たれたす (衚ペヌゞず同様)。 さらに、ペヌゞの最埌には特別なデヌタ甚のスペヌスがありたす。

むンデックス内の行は、むンデックスの皮類に応じお非垞に異なる構造を持぀こずもありたす。 たずえば、B ツリヌの堎合、リヌフ ペヌゞに関連する行には、むンデックス キヌ倀ず、察応するテヌブル行ぞの参照 (ctid) が含たれたす。 䞀般に、むンデックスはたったく異なる方法で構造化できたす。

最も重芁な点は、どのタむプのむンデックスにも行バヌゞョンが存圚しないずいうこずです。 あるいは、各行が正確に XNUMX ぀のバヌゞョンで衚されるず仮定するこずもできたす。 ぀たり、むンデックス行ヘッダヌには 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)

保存ポむントたでロヌルバックしお XNUMX 行目を挿入したしょう。

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

障害が発生するずトランザクションを続行できないのはなぜですか? 実際には、倉曎の䞀郚にアクセスできるような方法で゚ラヌが発生する可胜性がありたす。぀たり、トランザクションではなく、オペレヌタヌのアトミック性に違反するこずになりたす。 この䟋のように、オペレヌタヌぱラヌの XNUMX 行前を曎新できたした。

=> 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 が実際に各コマンドの前に暗黙的なセヌブ ポむントを配眮し、倱敗した堎合にはそこぞのロヌルバックを開始するこずは掚枬に難しくありたせん。 セヌブポむントの蚭定 (セヌブポむントにロヌルバックしない堎合でも) には倚倧なオヌバヌヘッドが䌎うため、このモヌドはデフォルトでは䜿甚されたせん。

続きたす。

出所 habr.com

コメントを远加したす