La mia ipotesi è che l'origine del problema sia un riferimento circolare di chiave esterna nelle tabelle.
TABLE vm_action_info
==> FOREIGN KEY (last_completed_vm_task_id) REFERENCES vm_task (id)
TABLE vm_task
==> CHIAVE ESTERA (vm_action_info_id) RIFERIMENTI vm_action_info (id)
La transazione è composta da due passaggi:
Quando due transazioni aggiorneranno lo stesso record in vm_action_info
table allo stesso tempo, questo terminerà con un deadlock.
Guarda un semplice test case:
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
;
Nella sessione 1 aggiungiamo un record a vm_task che fa riferimento a id=2 in vm_action_info
session1=> begin;
BEGIN
session1=> insert into vm_task values( 100, 0, 2 );
INSERT 0 1
session1=>
Contemporaneamente nella sessione 2 inizia un'altra transazione:
session2=> begin;
BEGIN
session2=> insert into vm_task values( 200, 0, 2 );
INSERT 0 1
session2=>
Quindi la 1a transazione esegue l'aggiornamento:
session1=> update vm_action_info set last_on_demand_task_id=100, version=version+1
session1=> where id=2;
ma questo comando si blocca ed è in attesa di un blocco.....
quindi la 2a sessione esegue l'aggiornamento ........
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=>
Rilevato deadlock !!!
Ciò è dovuto al fatto che entrambi gli INSERT in vm_task mettono un blocco condiviso sulla riga id=2 nella tabella vm_action_info a causa del riferimento alla chiave esterna. Quindi il primo aggiornamento tenta di inserire un blocco di scrittura su questa riga e si blocca perché la riga è bloccata da un'altra (seconda) transazione. Quindi il secondo aggiornamento tenta di bloccare lo stesso record in modalità di scrittura, ma è bloccato in modalità condivisa dalla prima transazione. E questo causa un deadlock.
Penso che questo possa essere evitato se si inserisce un blocco di scrittura nel record in vm_action_info, l'intera transazione deve consistere in 5 passaggi:
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;