2013-08-18 6 views
14

Sto lavorando con Postgres 9.1 e ottenendo un'eccezione deadlock in esecuzione eccessiva di un semplice metodo di aggiornamento.deadlock in postgres su semplice query di aggiornamento

Secondo i registri, il deadlock si verifica a causa dell'esecuzione di due aggiornamenti identici allo stesso tempo.

aggiornamento public.vm_action_info impostato last_on_demand_task_id = $ 1, la versione = versione + 1

Come fa due identici aggiornamenti semplici possono situazione di stallo tra di loro?

L'errore che sto ricevendo nel registro

2013-08-18 11:00:24 IDT HINT: See server log for query details. 
2013-08-18 11:00:24 IDT STATEMENT: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT ERROR: deadlock detected 
2013-08-18 11:00:25 IDT DETAIL: Process 31533 waits for ShareLock on transaction 4228275; blocked by process 31530. 
     Process 31530 waits for ExclusiveLock on tuple (0,68) of relation 70337 of database 69205; blocked by process 31533. 
     Process 31533: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
     Process 31530: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT HINT: See server log for query details. 
2013-08-18 11:00:25 IDT STATEMENT: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
2013-08-18 11:00:25 IDT ERROR: deadlock detected 
2013-08-18 11:00:25 IDT DETAIL: Process 31530 waits for ExclusiveLock on tuple (0,68) of relation 70337 of database 69205; blocked by process 31876. 
     Process 31876 waits for ShareLock on transaction 4228275; blocked by process 31530. 
     Process 31530: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 
     Process 31876: update public.vm_action_info set last_on_demand_task_id=$1, version=version+1 where id=$2 

lo schema è:

CREATE TABLE vm_action_info(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    vm_info_id integer NOT NULL, 
last_exit_code integer, 
    bundle_action_id integer NOT NULL, 
    last_result_change_time numeric NOT NULL, 
    last_completed_vm_task_id integer, 
    last_on_demand_task_id bigint, 
    CONSTRAINT vm_action_info_pkey PRIMARY KEY (id), 
    CONSTRAINT vm_action_info_bundle_action_id_fk FOREIGN KEY (bundle_action_id) 
     REFERENCES bundle_action (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE CASCADE, 
    CONSTRAINT vm_discovery_info_fk FOREIGN KEY (vm_info_id) 
     REFERENCES vm_info (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE CASCADE, 
    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, 

    CONSTRAINT vm_task_last_task_fk FOREIGN KEY (last_completed_vm_task_id) 
     REFERENCES vm_task (id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION 
) 
WITH (OIDS=FALSE); 

ALTER TABLE vm_action_info 
    OWNER TO vadm; 

-- Index: vm_action_info_vm_info_id_index 

-- DROP INDEX vm_action_info_vm_info_id_index; 

CREATE INDEX vm_action_info_vm_info_id_index 
    ON vm_action_info 
    USING btree (vm_info_id); 

CREATE TABLE vm_task 
(
    id integer NOT NULL, 
    version integer NOT NULL DEFAULT 0, 
    vm_action_info_id integer NOT NULL, 
    creation_time numeric NOT NULL DEFAULT 0, 
    task_state text NOT NULL, 
    triggered_by text NOT NULL, 
    bundle_param_revision bigint NOT NULL DEFAULT 0, 
    execution_time bigint, 
    expiration_time bigint, 
    username text, 
    completion_time bigint, 
    completion_status text, 
    completion_error text, 
    CONSTRAINT vm_task_pkey PRIMARY KEY (id), 
    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 
) 
WITH (
OIDS=FALSE 
); 
ALTER TABLE vm_task 
    OWNER TO vadm; 

-- Index: vm_task_creation_time_index 

-- DROP INDEX vm_task_creation_time_index  ; 

CREATE INDEX vm_task_creation_time_index 
    ON vm_task 
    USING btree 
(creation_time); 
+0

Essi non sono così semplici. C'è un costrutto FK sul campo (che si traduce in un indice che deve essere aggiornato) Forse provare differito inizialmente rinviato? (non credo che potrebbe fare alcuna differenza) – wildplasser

+1

Preferisco non cambiare il vincolo FK perché non sono completamente sicuro di come avrebbe effetto il sistema in generale. Aggiungendo una limitazione nel codice che solo una singola query può eseguire in un dato momento risolve il problema ma non capisco come una query possa causare un deadlock con se stessa. Tutte le serrature vengono acquisite nello stesso ordine, quindi in modo teorico non dovrebbe accadere. Esiste la possibilità che il postgres scopra in modo misterioso un deadlock che in realtà non esiste? – moshe

+0

Hai scritto "tutti i blocchi sono acquisiti nello stesso ordine", vuol dire che non è solo un semplice aggiornamento, ma l'intera transazione consiste di più comandi di blocco di questo singolo aggiornamento? Se sì, per favore mostraci l'intero codice. – krokodilko

risposta

4

potrebbe essere solo che il sistema è stato eccezionalmente occupato. Dici di averlo visto solo con "esecuzione eccessiva" della query.

Quello che sembra essere la situazione è questa:

pid=31530 wants to lock tuple (0,68) on rel 70337 (vm_action_info I suspect) for update 
    it is waiting behind pid=31533, pid=31876 
pid=31533 is waiting behind transaction 4228275 
pid=31876 is waiting behind transaction 4228275 

Così - abbiamo quello che sembra essere quattro operazioni tutte aggiornamento questa riga allo stesso tempo. La transazione 4228275 non ha ancora eseguito il commit o il rollback e sta trattenendo gli altri. Due di loro aspettavano il deadlock_timeout secondi altrimenti non vedremmo il timeout. Scade il timer, il rilevatore di deadlock dà un'occhiata, vede un mucchio di transazioni intrecciate e ne cancella una. Potrebbe non essere esattamente un punto morto, ma non sono sicuro che il rilevatore sia abbastanza intelligente da capirlo.

Prova uno di:

  1. ridurre il tasso di aggiornamenti
  2. avere un server più veloce
  3. Aumento deadlock_timeout

Probabilmente 3 # è il più semplice :-) potrebbe essere necessario impostare log_lock_waits anche tu puoi vedere se/quando il tuo sistema è sottoposto a questo tipo di sforzo.

+0

su un tasso di aggiornamento più lento questo scenario non succede Riguardo al suggerimento n. 3: in base alla documentazione postgres il parametro deadloak_timeout definisce solo la quantità di tempo prima che venga eseguito il meccanismo di deadlock detection e non ha alcun impatto sul fatto che venga dichiarata una situazione di deadlock. dalla documentazione: "la sua è la quantità di tempo, in millisecondi, di aspettare un blocco prima di controllare se c'è una condizione di deadlock. Il controllo per deadlock è relativamente costoso, quindi il server non lo esegue ogni volta aspetta un blocco " – moshe

+0

Un aggiornamento alla versione 9.2 potrebbe anche aiutare, ha diversi miglioramenti sul comportamento di blocco e sulla velocità generale. –

+0

La condizione è un deadlock, a meno che una delle transazioni non venga interrotta. –

14

La mia ipotesi è che la fonte del problema sia un riferimento di chiave esterna circolare nelle tabelle.

TABELLA vm_action_info
==> FOREIGN KEY (last_completed_vm_task_id) RIFERIMENTI vm_task (id)

TABELLA vm_task
==> FOREIGN KEY (vm_action_info_id) RIFERIMENTI vm_action_info (id)

L'operazione consiste di due fasi:

  1. aggiungere una nuova voce alla task tavolo
  2. aggiornamenti voce nel vm_action_in corrispondenti per la tabella vm_task.

Quando due transazioni stanno per aggiornare lo stesso record nella tabella vm_action_info, allo stesso tempo, questo si concluderà con una situazione di stallo.

sguardo al semplice caso di test:

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 sessione 1 si aggiunge un record a vm_task che il riferimento a id = 2 in vm_action_info

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

Allo stesso tempo, in sessione 2 un inizia un'altra transazione:

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

Quindi la prima transazione esegue l'aggiornamento ate:

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

ma questo comando si blocca ed è in attesa di un blocco .....

poi la 2a sessione esegue l'aggiornamento ........

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

Questo perché entrambi gli INSERT in vm_task posizionano un blocco condiviso sulla riga id = 2 nella tabella vm_action_info a causa del riferimento alla chiave esterna. Quindi il primo aggiornamento tenta di posizionare un blocco di scrittura su questa riga e si blocca perché la riga è bloccata da un'altra (seconda) transazione. Quindi il secondo aggiornamento tenta di bloccare lo stesso record in modalità di scrittura, ma è bloccato in modalità condivisa dalla prima transazione. E questo causa un punto morto.

Penso che questo può essere evitato se si inserisce un blocco di scrittura su disco nel vm_action_info, l'intera transazione deve essere costituito da 5 fasi:

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;