バッファ管理。タプルの追加時のFKチェック

【PostgreSQLウォッチ】第20回 PostgreSQL 8.1ベータ・テスト開始,新機能ロールと共有行ロックを参考にFKのチェックが行われる流れを確認した。

まず準備としてリンク先と同じように次のようなSQLpsqlで実行。

CREATE TABLE t1(i INTEGER PRIMARY KEY, j INTEGER);
CREATE TABLE t2(i INTEGER PRIMARY KEY, j INTEGER REFERENCES t1(i));
INSERT INTO t1 VALUES (1, 10), (2, 20), (3, 30);

それから、FKのチェックが行われるはずの次のSQLを実行。

BEGIN;
INSERT INTO t2 VALUES(10, 1);

このとき次のことが行われた。

  • まず、heap_insert()でタプルの追加が行われる。
  • タプルの追加処理が終わった後はAfterTriggerEndQuery()から始まる処理でFKのチェックが開始される。
  • ri_PerformCheck()で、SELECT 〜 FOR SHARE なクエリが組み立てられ実行される。
  • 実際のロックの処理(タプルにロック用ビットを立てページをダーティにする)はheap_lock_tuple()で行われる。

GDBで確認した処理のシーケンスはこんな感じ。

(gdb) bt
#0  heap_lock_tuple (relation=0xb562e080, tuple=0xbfc4c914, buffer=0xbfc4c910, ctid=0xbfc4c90a, update_xmax=0xbfc4c904, cid=1, mode=LockTupleShared, nowait=0 '\0') at heapam.c:3275
#1  0x0818d27d in ExecutePlan (estate=0x84ea1f0, planstate=0x84ea3c0, operation=CMD_SELECT, numberTuples=1, direction=ForwardScanDirection, dest=0x8401948) at execMain.c:1395
#2  0x0818b98c in ExecutorRun (queryDesc=0x84e8228, direction=ForwardScanDirection, count=1) at execMain.c:270
#3  0x081aca1c in _SPI_pquery (queryDesc=0x84e8228, fire_triggers=0 '\0', tcount=1) at spi.c:1820
#4  0x081ac71e in _SPI_execute_plan (plan=0x852cba8, Values=0xbfc4cc04, Nulls=0xbfc4cbc4 " +N\b\002", snapshot=0x0, crosscheck_snapshot=0x0, read_only=0 '\0', fire_triggers=0 '\0', tcount=1) at spi.c:1669
#5  0x081aa204 in SPI_execute_snapshot (plan=0x852cba8, Values=0xbfc4cc04, Nulls=0xbfc4cbc4 " +N\b\002", snapshot=0x0, crosscheck_snapshot=0x0, read_only=0 '\0', fire_triggers=0 '\0', tcount=1) at spi.c:395
#6  0x082dfcfb in ri_PerformCheck (qkey=0xbfc4d078, qplan=0x852cba8, fk_rel=0xb5631a58, pk_rel=0xb562e080, old_tuple=0x0, new_tuple=0xbfc4d614, detectNewRows=0 '\0', expect_OK=5, constrname=0xbfc4d114 "t2_j_fkey") at ri_triggers.c:3340
#7  0x082da911 in RI_FKey_check (fcinfo=0xbfc4d3c8) at ri_triggers.c:491
#8  0x082da989 in RI_FKey_check_ins (fcinfo=0xbfc4d3c8) at ri_triggers.c:516
#9  0x0816fac3 in ExecCallTriggerFunc (trigdata=0xbfc4d63c, tgindx=0, finfo=0x8500470, instr=0x0, per_tuple_context=0x84e2190) at trigger.c:1577
#10 0x08170e2c in AfterTriggerExecute (event=0x84f8208, rel=0xb5631a58, trigdesc=0x85002f0, finfo=0x8500470, instr=0x0, per_tuple_context=0x84e2190) at trigger.c:2475
#11 0x08171139 in afterTriggerInvokeEvents (query_depth=0, firing_id=0, estate=0x8500220, delete_ok=1 '\001') at trigger.c:2680
#12 0x0817144b in AfterTriggerEndQuery (estate=0x8500220) at trigger.c:2871
#13 0x0824dee7 in ProcessQuery (plan=0x84ad900, params=0x0, dest=0x84ad970, completionTag=0xbfc4d8da "INSERT 0 1") at pquery.c:216
#14 0x0824f248 in PortalRunMulti (portal=0x8506208, isTopLevel=1 '\001', dest=0x84ad970, altdest=0x84ad970, completionTag=0xbfc4d8da "INSERT 0 1") at pquery.c:1242
#15 0x0824ea27 in PortalRun (portal=0x8506208, count=2147483647, isTopLevel=1 '\001', dest=0x84ad970, altdest=0x84ad970, completionTag=0xbfc4d8da "INSERT 0 1") at pquery.c:813
#16 0x0824939c in exec_simple_query (query_string=0x84ac870 "INSERT INTO t2 VALUES(10, 1);") at postgres.c:986
#17 0x0824d15f in PostgresMain (argc=4, argv=0x8454520, username=0x84544f8 "postgres") at postgres.c:3572
#18 0x08217982 in BackendRun (port=0x8467d18) at postmaster.c:3207
#19 0x08216f0a in BackendStartup (port=0x8467d18) at postmaster.c:2830
#20 0x08214928 in ServerLoop () at postmaster.c:1274
#21 0x08214335 in PostmasterMain (argc=1, argv=0x8451578) at postmaster.c:1029
#22 0x081b6707 in main (argc=1, argv=0x8451578) at main.c:188

おもしろいのは、

  • ロックを取得するために別途クエリを組み立てて実行していること。
  • 順番が、ロックを取得してからタプル追加、ではなく、タプルを追加してからロックの取得となっていること。
    • MVCC的にはどっちからやっても同じ意味なのだけど、効率を考えてこの順番なのかなと思った。

ロックの開放はトランザクション?(クエリ?)の完了時に行われるはずだけど、それはどこだろう?明日調べる。