改良の内容
●
簡潔に言うと
1. 新しい行ロックモードが導入された
• 今までは FORSHARE と FOR UPDATE の2つ
• 今回新たに FOR KEY SHARE と
FOR NO KEY UPDATE が加わった
2. 外部キーでこの新しいロックモードを使うことで、競合
が減って性能向上となる
• 外部キーの参照テーブルへのINSERT時は、被参照テーブルの該
当キー保持列にFOR KEY SHAREが実施される
•
ユーザが被参照テーブルに対し、キーを変更しない更新処理を
行う場合に自動でFOR NO KEY UPDATEが使われる
• FOR KEY SHARE と FOR NO KEY UPDATEは競合しない!
4.
新しいロックモード
FOR UPDATE FORNO KEY
UPDATE
FOR SHARE FOR KEY SHARE
FOR UPDATE
× × × ×
FOR NO KEY
UPDATE × × ×
FOR SHARE
× ×
FOR KEY
SHARE ×
FOR NO KEY UPDATE は キー(外部キーとしての被参照)列の更新
を行わない更新時に使われる。
FOR KEY SHARE は、弱いロックで、対象列のキー列が変更され
ないことだけを保証したい場合に使われる。
5.
分かりづらいので、昔を振り返りつつ
●
簡単な仕組みのおさらい
– PostgreSQL は外部キーをトリガで実現している
–参照テーブル、被参照テーブルについて、
TRIGGER EACH ROWSを仕掛けておき、矛盾す
る更新処理は弾くようになっている
ORDER_ID ITEM_ID DELIVER
1 1 2012-01-01
2 2 2012-01-02
ITEM_ID NAME STOCK
1 ペン 60
2 紙 80
3 寒天 1000
INSERT INTO ORDER
VALUES (1, 4, now());
ITEM_ID の 4は無い!
RI_FKey_check_in
s
制約違反!
ORDERテーブル ITEMテーブル
6.
寄り道
● PostgreSQLで外部キーを設定すると自動的にトリ
ガが付きますが、どんなトリガなのかはシステムカ
タログやsrc/backend/utils/adt/ri_triggers.cを見ると
分かります。
=# SELECTproname, prosrc
FROM pg_proc p, pg_trigger t
WHERE p.oid = t.tgfoid AND t.tgrelid = '被参照テーブル'::regclass;
proname | prosrc
----------------------+----------------------
RI_FKey_noaction_del | RI_FKey_noaction_del
RI_FKey_noaction_upd | RI_FKey_noaction_upd
(2 rows)
=# SELECT proname , prosrc
FROM pg_proc p, pg_trigger t
WHERE p.oid = t.tgfoid AND t.tgrelid = '参照テーブル'::regclass;
proname | prosrc
-------------------+-------------------
RI_FKey_check_ins | RI_FKey_check_ins
RI_FKey_check_upd | RI_FKey_check_upd
(2 rows)
7.
PG8.0までの問題
● 参照テーブルへINSERTすると、被参照テーブル
の当該キーのレコードへFOR UPDATEでロック
をかけていた
–被参照テーブルで、参照テーブルが必要とするレコー
ドが無くなる(キー更新やDELETE)と困るから
ORDER_ID ITEM_ID DELIVER
1 1 2012-01-01
2 2 2012-01-02
ITEM_ID NAME STOCK
1 ペン 60
2 紙 80
3 寒天 1000
INSERT INTO ORDER
VALUES (1, 3, now());
ITEM_ID の 3の変更阻止
RI_FKey_check_ins
DELETE FROM ITEM
WHERE ITEM_ID = 3;
ブロック!
ORDERテーブル ITEMテーブル
8.
PG8.0までの問題
●
そのため、デッドロックの温床になりやすかった
ORDER_ID ITEM_ID DELIVER
11 2012-01-01
2 2 2012-01-02
ITEM_ID NAME STOCK
1 ペン 60
2 紙 80
3 寒天 1000
INSERT INTO ORDER
VALUES (1, 1, now());
INSERT INTO ORDER
VALUES (1, 2, now())
INSERT INTO ORDER
VALUES (1, 2, now());
INSERT INTO ORDER
VALUES (1, 1, now())
背後でデッドロック・・
9.
そこで、PG8.1から共有ロック導入
● 参照テーブルへのINSERT時には、被参照テーブルへ排他
ではなく共有ロックを取れるように改良
– FORSHARE ロックモードが導入され、被参照テーブルへの
ロックはこれを使うようになった
– FOR SHARE 同士は競合しない!
ORDER_ID ITEM_ID DELIVER
1 1 2012-01-01
2 2 2012-01-02
ITEM_ID NAME STOCK
1 ペン 60
2 紙 80
3 寒天 1000
INSERT INTO ORDER
VALUES (1, 1, now());
INSERT INTO ORDER
VALUES (1, 2, now())
INSERT INTO ORDER
VALUES (1, 2, now());
INSERT INTO ORDER
VALUES (1, 1, now())
共有ロック同士は競合しない!
更新処理やFOR UPDATEとは
競合