【 PostgreSQL】2つのトランザクションから主キー制約違反となるデータをINSERTしたときの挙動
動作確認はPostgreSQL 13.0、トランザクションの分離レベルはread committedで行っています。
検証用テーブルの作成
以下のようにidを主キーとするテーブルを作成します。
create table employee(
id int not null,
name text not null,
constraint pk_employee primary key(id)
);
検証
ローカルでターミナルを2つ起動しそれぞれトランザクションを開始します。そして、以下の図のような順序でINSERT, COMMITを行います。
ターミナル1でid=1のデータをINSERTした後にターミナル2でもid=1のデータをINSERTすると、ターミナル2は待ち状態となります。ターミナル1でCOMMITするとid=1のデータのINSERT完了し、ターミナル2のINSERTは主キー制約違反で失敗します。
PostgreSQLの公式ドキュメント61.5. インデックス一意性検査には一意制約を持つテーブルに対して同じ一意キーをinsertした際の挙動について以下のように記述されています。
競合する行が未コミットのトランザクションで挿入された場合、挿入しようとしている方はトランザクションのコミットが分かるまで待機しなければなりません。 ロールバックした場合は競合しません。 競合する行が削除されずにコミットした場合、一意性違反となります。 (具体的には、他のトランザクションの終了をただ待機し、終了後に可視性の検査を完全に再実行します。)
PostgreSQL 13.1文書 61.5. インデックス一意性検査
主キー制約は一意制約を持つため、同じ主キーを持つデータを2つのトランザクションからinsertすると一方のトランザクションは待ち状態となります。
次に、以下の図のようにターミナル1のトランザクションをrollbackします。
ターミナル1のトランザクションがrollbackされるとターミナル2のINSERTが待ち状態から復帰し処理成功となり、COMMITも成功します。