PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

deadlock no postgres na consulta de atualização simples


Meu palpite é que a origem do problema é uma referência de chave estrangeira circular em suas tabelas.

TABLE vm_action_info
==> FOREIGN KEY (last_completed_vm_task_id) REFERENCES vm_task (id)

TABELA vm_task
==> FOREIGN KEY (vm_action_info_id) REFERÊNCIAS vm_action_info (id)

A transação consiste em duas etapas:

Quando duas transações vão atualizar o mesmo registro no vm_action_info table ao mesmo tempo, isso terminará com um deadlock.

Veja um caso de teste simples:
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
      ;


Na sessão 1 adicionamos um registro a vm_task que faz referência a id=2 em vm_action_info
session1=> begin;
BEGIN
session1=> insert into vm_task values( 100, 0, 2 );
INSERT 0 1
session1=>

Ao mesmo tempo, na sessão 2, uma outra transação começa:
session2=> begin;
BEGIN
session2=> insert into vm_task values( 200, 0, 2 );
INSERT 0 1
session2=>

Em seguida, a 1ª transação realiza a atualização:
session1=> update vm_action_info set last_on_demand_task_id=100, version=version+1
session1=> where id=2;

mas este comando trava e está aguardando um bloqueio.....

então a 2ª sessão realiza a atualização ........
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=>

Deadlock detectado !!!

Isso ocorre porque ambos os INSERTs em vm_task colocam um bloqueio compartilhado na linha id=2 na tabela vm_action_info devido à referência de chave estrangeira. Em seguida, a primeira atualização tenta colocar um bloqueio de gravação nesta linha e trava porque a linha está bloqueada por outra (segunda) transação. Em seguida, a segunda atualização tenta bloquear o mesmo registro no modo de gravação, mas é bloqueado no modo compartilhado pela primeira transação. E isso causa um impasse.

Acho que isso pode ser evitado se você colocar um bloqueio de gravação no registro em vm_action_info, toda a transação deve consistir em 5 etapas:
 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;