sql >> Base de Datos >  >> RDS >> PostgreSQL

interbloqueo en postgres en consulta de actualización simple

Supongo que el origen del problema es una referencia de clave externa circular en sus tablas.

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

TABLA vm_task
==> CLAVE EXTERNA (vm_action_info_id) REFERENCIAS vm_action_info (id)

La transacción consta de dos pasos:

Cuando dos transacciones van a actualizar el mismo registro en el vm_action_info mesa al mismo tiempo, esto terminará con un interbloqueo.

Mire el caso de prueba simple:

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
      ;


En la sesión 1 agregamos un registro a vm_task que hace referencia a id=2 en vm_action_info

session1=> begin;
BEGIN
session1=> insert into vm_task values( 100, 0, 2 );
INSERT 0 1
session1=>

Al mismo tiempo en la sesión 2 comienza otra transacción:

session2=> begin;
BEGIN
session2=> insert into vm_task values( 200, 0, 2 );
INSERT 0 1
session2=>

Luego, la primera transacción realiza la actualización:

session1=> update vm_action_info set last_on_demand_task_id=100, version=version+1
session1=> where id=2;

pero este comando se bloquea y está esperando un bloqueo...

entonces la segunda sesión realiza la actualización ........

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=>

Interbloqueo detectado !!!

Esto se debe a que ambos INSERT en vm_task colocan un bloqueo compartido en la fila id=2 en la tabla vm_action_info debido a la referencia de clave externa. Luego, la primera actualización intenta colocar un bloqueo de escritura en esta fila y se bloquea porque la fila está bloqueada por otra (segunda) transacción. Luego, la segunda actualización intenta bloquear el mismo registro en modo de escritura, pero la primera transacción lo bloquea en modo compartido. Y esto causa un interbloqueo.

Creo que esto se puede evitar si coloca un bloqueo de escritura en el registro en vm_action_info, toda la transacción debe constar de 5 pasos:

 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;