PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

blocage dans postgres sur une simple requête de mise à jour

Je suppose que la source du problème est une référence de clé étrangère circulaire dans vos tables.

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

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

La transaction se compose de deux étapes :

Lorsque deux transactions vont mettre à jour le même enregistrement dans le vm_action_info table en même temps, cela se terminera par un blocage.

Regardez un cas de test 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
      ;


Dans la session 1, nous ajoutons un enregistrement à vm_task qui fait référence à id=2 dans vm_action_info

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

Au même moment dans la session 2 une autre transaction commence :

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

Puis la 1ère transaction effectue la mise à jour :

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

mais cette commande se bloque et attend un verrou.....

puis la 2ème session effectue la mise à jour ........

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

Blocage détecté !!!

C'est parce que les deux INSERT dans vm_task placent un verrou partagé sur la ligne id=2 dans la table vm_action_info en raison de la référence de clé étrangère. Ensuite, la première mise à jour tente de placer un verrou en écriture sur cette ligne et se bloque car la ligne est verrouillée par une autre (seconde) transaction. Ensuite, la deuxième mise à jour tente de verrouiller le même enregistrement en mode écriture, mais il est verrouillé en mode partagé par la première transaction. Et cela provoque un blocage.

Je pense que cela peut être évité si vous placez un verrou en écriture sur l'enregistrement dans vm_action_info, l'ensemble de la transaction doit consister en 5 étapes :

 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;