私の推測では、問題の原因はテーブル内の循環外部キー参照です。
TABLE vm_action_info
==>外部キー(last_completed_vm_task_id)参照vm_task(id)
テーブルvm_task
==>外部キー(vm_action_info_id)参照vm_action_info(id)
トランザクションは、次の2つのステップで構成されます。
2つのトランザクションがvm_action_info
の同じレコードを更新する場合 テーブルと同時に、これはデッドロックで終了します。
簡単なテストケースを見てください:
CREATE TABLE vm_task
(
id integer NOT NULL,
version integer NOT NULL DEFAULT 0,
vm_action_info_id integer NOT NULL,
CONSTRAINT vm_task_pkey PRIMARY KEY (id )
)
WITH ( OIDS=FALSE );
insert into vm_task values
( 0, 0, 0 ), ( 1, 1, 1 ), ( 2, 2, 2 );
CREATE TABLE vm_action_info(
id integer NOT NULL,
version integer NOT NULL DEFAULT 0,
last_on_demand_task_id bigint,
CONSTRAINT vm_action_info_pkey PRIMARY KEY (id )
)
WITH (OIDS=FALSE);
insert into vm_action_info values
( 0, 0, 0 ), ( 1, 1, 1 ), ( 2, 2, 2 );
alter table vm_task
add CONSTRAINT vm_action_info_fk FOREIGN KEY (vm_action_info_id)
REFERENCES vm_action_info (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
;
Alter table vm_action_info
add CONSTRAINT vm_task_last_on_demand_task_fk FOREIGN KEY (last_on_demand_task_id)
REFERENCES vm_task (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
;
セッション1では、vm_action_infoのid=2を参照するレコードをvm_taskに追加します
session1=> begin;
BEGIN
session1=> insert into vm_task values( 100, 0, 2 );
INSERT 0 1
session1=>
同時に、セッション2で別のトランザクションが開始されます:
session2=> begin;
BEGIN
session2=> insert into vm_task values( 200, 0, 2 );
INSERT 0 1
session2=>
次に、最初のトランザクションが更新を実行します:
session1=> update vm_action_info set last_on_demand_task_id=100, version=version+1
session1=> where id=2;
しかし、このコマンドはハングし、ロックを待機しています.....
次に、2番目のセッションが更新を実行します........
session2=> update vm_action_info set last_on_demand_task_id=200, version=version+1 where id=2;
BŁĄD: wykryto zakleszczenie
SZCZEGÓŁY: Proces 9384 oczekuje na ExclusiveLock na krotka (0,5) relacji 33083 bazy danych 16393; zablokowany przez 380
8.
Proces 3808 oczekuje na ShareLock na transakcja 976; zablokowany przez 9384.
PODPOWIEDŹ: Przejrzyj dziennik serwera by znaleźć szczegóły zapytania.
session2=>
デッドロックが検出されました!!!
これは、vm_taskへの両方のINSERTが、外部キー参照のためにvm_action_infoテーブルの行id=2に共有ロックを設定するためです。次に、最初の更新でこの行に書き込みロックを設定しようとしますが、行が別の(2番目の)トランザクションによってロックされているためにハングします。次に、2番目の更新は書き込みモードで同じレコードをロックしようとしますが、最初のトランザクションによって共有モードでロックされます。そして、これによりデッドロックが発生します。
vm_action_infoのレコードに書き込みロックを設定すると、トランザクション全体を5つのステップで構成する必要があるため、これを回避できると思います。
begin;
select * from vm_action_info where id=2 for update;
insert into vm_task values( 100, 0, 2 );
update vm_action_info set last_on_demand_task_id=100,
version=version+1 where id=2;
commit;