PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

impas w postgresie przy prostym zapytaniu o aktualizację

Domyślam się, że źródłem problemu jest cykliczne odwołanie do klucza obcego w twoich tabelach.

TABELA vm_action_info
==> KLUCZ OBCY (last_completed_vm_task_id) REFERENCJE vm_task (id)

TABELA vm_task
==> KLUCZ OBCY (vm_action_info_id) REFERENCJE vm_action_info (id)

Transakcja składa się z dwóch kroków:

Gdy dwie transakcje będą aktualizować ten sam rekord w vm_action_info tabeli w tym samym czasie, zakończy się to impasem.

Spójrz na prosty przypadek testowy:

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
      ;


W sesji 1 dodajemy do vm_task rekord odwołujący się do id=2 w vm_action_info

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

W tym samym czasie w sesji 2 rozpoczyna się kolejna transakcja:

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

Następnie pierwsza transakcja wykonuje aktualizację:

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

ale to polecenie zawiesza się i czeka na blokadę....

potem druga sesja wykonuje aktualizację ........

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

Wykryto zakleszczenie !!!

Dzieje się tak, ponieważ obie wstawki INSERT w vm_task umieszczają wspólną blokadę w wierszu id=2 w tabeli vm_action_info ze względu na odwołanie do klucza obcego. Następnie pierwsza aktualizacja próbuje umieścić blokadę zapisu w tym wierszu i zawiesza się, ponieważ wiersz jest zablokowany przez inną (drugą) transakcję. Następnie druga aktualizacja próbuje zablokować ten sam rekord w trybie zapisu, ale jest on blokowany w trybie współdzielonym przez pierwszą transakcję. A to powoduje impas.

Myślę, że można tego uniknąć, jeśli umieścisz blokadę zapisu w vm_action_info, cała transakcja musi składać się z 5 kroków:

 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;


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Postgres:współbieżne zapytania w połączeniu

  2. Jak rozwiązać niejednoznaczne dopasowanie podczas łączenia wygenerowanych klas Jooq

  3. Reprezentowanie dat, godzin i interwałów w PostgreSQL

  4. pypyodbc pomaga znaleźć samouczki na początek

  5. Google Cloud SQL PG11:nie można zmienić rozmiaru segmentu pamięci współdzielonej