PostgreSQL
PostgreSQL10

トランザクションIDに関する関数の追加

はじめに

にゃーん
この記事は、PostgreSQL 10全部ぬこ Advent Calendar 2017 の18日目のエントリです。
うー、あと一週間。なんとか完走できるといいなー。

今日はPostgreSQL 10で追加されたトランザクションIDに関するSQL関数について軽く。
PostgresQL 10では以下の2関数が追加された。

関数名 引数 返却型 概要
txid_current_if_assigned() なし bigint トランザクションIDが既に割り当てられている場合は、txid_current()と同様にトランザクションIDを返却する。トランザクションIDが割り当てられていない状態だと、トランザクションIDをアサインする代わりにnullを返却する
txid_status() xid bigint txid_status xidで与えたトランザクションIDの状態(commited, abort, in progress, null)を返却する。

PostgreSQLでは慣習的にトランザクションのことを"tx"や"x"と表記する文化らしく、このためトランザクションIDも"xid"と表記することが多い。また、それがSQL関数の名前にも使われている。

txid_current_if_assigned()

psqlで検証する場合、以下のような準備をしておくと見やすい。

xid=# \pset null (null)
Null display is "(null)".
xid=# 

こうしておくと、nullが返却されたときに(null)と表示されるので見やすくなる。

まず、以前から存在していたtxid_current()を実行する。
この関数は新たにトランザクションIDを割り当て、そのトランザクションIDを返却する。

xid=# SELECT txid_current();
 txid_current 
--------------
      1287942
(1 row)

同じようにtxid_current_if_assigned()を実行した場合、発行時点ではトランザクションIDは割り当てられていないので、nullが返却される。

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)

ここまではpsqlのAUTOCOMMITモード上での動作となる。
今度は、明示的にトランザクションを開始し、そのトランザクション内で、txid_current()と、txid_current_if_assigned()を実行してみる。

xid=# BEGIN;
BEGIN
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------

(1 row)

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------

(1 row)

xid=# SELECT txid_current();
 txid_current 
--------------
      1287950
(1 row)

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287950
(1 row)

xid=# ROLLBACK;
ROLLBACK
xid=# 

トランザクション開始直後にtxid_current_if_assigned()を実行してもnullである。
明示的にtxid_current()を実行した後だと、txid_current_if_assigned()も同じトランザクションIDを返却する。

トランザクションIDは、BEGINでトランザクションを開始した後には払い出されない。INSERT/UPDATE/DELETE等の更新文が発行される契機でトランザクションIDが発行される。

xid=# BEGIN;
BEGIN
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)

xid=# INSERT INTO test VALUES (1, 'aaa');
INSERT 0 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287952
(1 row)

で、この関数、どういうときに使うのか。実はあんまり自分でもよく分かっていない。
でも、psql上で使うなら、 \if, \then, \els \endif などと組合せて、トランザクションIDの有無で、後続のSQL発行を分岐させたりするのに役に立つのかも。

txid_status()

実行例

トランザクションを開始し、その直後にtxid_current_if_assignedを実行してもnullが返却されるのは、上で説明したとおり。
では、このnullのトランザクションIDをtxid_status()に与えると・・・nullが返却される。ふむ。

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)

xid=# SELECT txid_status(null);
 txid_status 
-------------
 (null)
(1 row)

同じトランザクション内で、INSERT文を実行する。これでトランザクションIDは払い出されるはず。

xid=# INSERT INTO test VALUES (1, 'aaa');
INSERT 0 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287956
(1 row)

xid=# SELECT txid_status(1287956);
 txid_status 
-------------
 in progress
(1 row)

払い出されたトランザクションID(1287956)をtxid_status()に与えると、in progressと表示される。
さらにこの状態でCOMMITして、その後に同じトランザクションIDの状態を見ると

xid=# COMMIT;
COMMIT
xid=# SELECT txid_status(1287956);
 txid_status 
-------------
 committed
(1 row)

このようにcommittedと表示される。

トランザクションがロールバックした場合には、

xid=# BEGIN;
BEGIN
xid=# DELETE FROM test;
DELETE 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287957
(1 row)

xid=# SELECT txid_status(1287957);
 txid_status 
-------------
 in progress
(1 row)

xid=# ROLLBACK;
ROLLBACK
xid=# SELECT txid_status(1287957);
 txid_status 
-------------
 aborted
(1 row)

このようにabortedと表示される。

この関数の用途は?

マニュアルをちら見した感じだと、二層コミットの状態確認なんかに使えるようにも思える。

良くわからない挙動・・・

で、これを動かしていて、「ん?」という動作があった。

xid=# BEGIN;
BEGIN
xid=# DELETE FROM test;
DELETE 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287960
(1 row)

で、今のトランザクションIDをtxid_status()に与えてみる。

xid=# SELECT txid_status(1287960);
 txid_status 
-------------
 in progress
(1 row)

うん。ここまではわかる。で、今のトランザクションIDから1つ進めた数をtxid_status()に与えてみる。

xid=# SELECT txid_status(1287961);
 txid_status 
-------------
 in progress
(1 row)

えええ、これもin progressになるのか。
さらに1つ先の数を指定すると、

xid=# SELECT txid_status(1287962);
ERROR:  transaction ID 1287962 is in the future

今度はエラーになってしまった。
エラーになったので仕方がない。ROLLBACKしよう。

xid=# ROLLBACK;
ROLLBACK

で、ROLLBACKしたあと、1287960, 1287961のトランザクションIDの状態を見ると、

xid=# SELECT txid_status(1287960);
 txid_status 
-------------
 aborted
(1 row)

xid=# SELECT txid_status(1287961);
 txid_status 
-------------
 in progress
(1 row)

xid=# 

1287960はabortedに、1287961はin progressになる。これが正しい挙動なのかどうか良くわからない・・・。

おまけ

関数xid_status()は、backend/utils/adt/txid.cの792行目(PostgreSQL 10.1の場合)あたりに実装コードがある。
で、ERROR: transaction ID 1287962 is in the futureのエラーメッセージは、その関数内で最初のほうに実行しているTransactionIdInRecentPast()の中で出力されているっぽい。
現状の作りはそうなんだー、なんだけどせめてPostgreSQL文書のxid_status()関数の説明のところには、ERRORになるケースを書いてもいいんじゃないかなー。

とふと思った。

参考:該当するリリースノート

本エントリに関連するPostgreSQL 10リリースノートの記載です。

E.2.3.1.5. General Performance

  • Add function txid_current_if_assigned() to return the current transaction ID or NULL if no transaction ID has been assigned (Craig Ringer)
  • Add function txid_status() to check if a transaction was committed (Craig Ringer)