PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Deadlock in Postgres bei einfacher Update-Abfrage

Meine Vermutung ist, dass die Quelle des Problems eine zirkuläre Fremdschlüsselreferenz in Ihren Tabellen ist.

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)

Die Transaktion besteht aus zwei Schritten:

Wenn zwei Transaktionen denselben Datensatz in vm_action_info aktualisieren werden -Tabelle zur gleichen Zeit, endet dies mit einem Deadlock.

Schauen Sie sich den einfachen Testfall an:

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
      ;


In Sitzung 1 fügen wir einen Datensatz zu vm_task hinzu, der auf id=2 in vm_action_info verweist

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

Gleichzeitig beginnt in Session 2 eine weitere Transaktion:

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

Dann führt die 1. Transaktion die Aktualisierung durch:

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

aber dieser Befehl hängt und wartet auf eine Sperre.....

dann führt die 2. Sitzung das Update durch ........

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 erkannt !!!

Dies liegt daran, dass beide INSERTs in vm_task aufgrund der Fremdschlüsselreferenz eine gemeinsame Sperre auf Zeile id=2 in der Tabelle vm_action_info platzieren. Dann versucht das erste Update, eine Schreibsperre auf dieser Zeile zu setzen und hängt, weil die Zeile durch eine andere (zweite) Transaktion gesperrt ist. Dann versucht die zweite Aktualisierung, denselben Datensatz im Schreibmodus zu sperren, aber er wird durch die erste Transaktion im freigegebenen Modus gesperrt. Und dies führt zu einem Deadlock.

Ich denke, dass dies vermieden werden kann, wenn Sie eine Schreibsperre auf den Datensatz in vm_action_info setzen, die gesamte Transaktion muss aus 5 Schritten bestehen:

 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;