Errore PostgreSQL: deadlock detected
ERROR: deadlock detected
DETAIL: Process 18461 waits for ShareLock on transaction 1064; blocked by process 18463.
Process 18463 waits for ShareLock on transaction 1063; blocked by process 18461.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,3) in relation "accounts"
Due (o più) transazioni stanno ciascuna aspettando un lock che l'altra tiene. Nessuna potrà mai proseguire, così PostgreSQL rileva il ciclo, sceglie una transazione come vittima e la interrompe con questo errore. L'altra transazione prosegue normalmente.
Cosa significa questo errore
I lock a livello di riga in PostgreSQL sono tenuti fino al commit o al rollback della transazione. Se la transazione A blocca la riga 1 e poi vuole la riga 2, mentre la transazione B tiene la riga 2 e vuole la riga 1, si forma un ciclo: un deadlock. Quando un backend ha atteso su un lock per più tempo di deadlock_timeout (default: 1 secondo), PostgreSQL esegue un controllo dei deadlock; se trova un ciclo, interrompe uno dei partecipanti con lo SQLSTATE 40P01, parente del 40001.
Un inquadramento importante: nel momento in cui vedi l'errore, il database ha già risolto la situazione. Niente è corrotto e la transazione sopravvissuta ha completato la sua attesa sul lock. Quello che resta è un problema applicativo — la transazione interrotta è stata sottoposta a rollback e il suo lavoro è perso, quindi la tua applicazione deve essere pronta a riprovarla.
Cause comuni
- Aggiornare le stesse righe in ordini diversi. Il caso classico: il job A aggiorna il conto 1 poi il conto 2, il job B aggiorna il conto 2 poi il conto 1. Due qualsiasi writer multi-riga senza un ordine concordato possono andare in deadlock.
- Toccare le tabelle in ordini diversi nei vari percorsi di codice — es. un percorso scrive
orderspoiinventory, un altro scriveinventorypoiorders. - Chiavi esterne. Inserire o aggiornare una riga figlia acquisisce uno share lock sulla riga padre referenziata. Mescolato ad aggiornamenti concorrenti del padre, questo produce deadlock che sembrano misteriosi perché nessuna query menziona entrambe le tabelle.
- Upgrade di lock: leggere una riga con
SELECT ... FOR SHAREe aggiornarla in seguito consente a due transazioni di acquisire lo share lock e poi bloccarsi a vicenda sull'upgrade. - Le transazioni lunghe non causano deadlock di per sé, ma tengono i lock più a lungo e allargano la finestra per ogni pattern qui sopra.
Come diagnosticarlo
L'errore stesso contiene la maggior parte di ciò che serve:
DETAILelenca i process ID e quale transazione ciascuno stava aspettando.CONTEXTspesso indica la tupla e la relation in scrittura.- Il log del server a quel timestamp registra le query coinvolte — è ciò a cui punta l'HINT.
Imposta log_lock_waits = on per loggare anche ogni attesa su un lock più lunga di deadlock_timeout, che ti mostra i quasi-incidenti, non solo le collisioni. Per l'analisi live di chi blocca chi:
SELECT waiting.pid AS waiting_pid, waiting.query AS waiting_query,
blocking.pid AS blocking_pid, blocking.query AS blocking_query
FROM pg_stat_activity waiting
JOIN pg_stat_activity blocking
ON blocking.pid = ANY (pg_blocking_pids(waiting.pid));
Come risolverlo
- Concorda un ordine globale dei lock. Quando una transazione scrive più righe, acquisisci prima i lock in un ordine deterministico:
SELECT id FROM accounts WHERE id IN (7, 3, 42) ORDER BY id FOR UPDATE; -- poi gli UPDATE, in qualsiasi ordine - Tocca le tabelle nello stesso ordine ovunque (documenta l'ordine; non importa quale, solo che sia coerente).
- Tieni le transazioni brevi. Non aspettare mai input dell'utente, chiamate HTTP o code mentre tieni lock di riga.
- Riprova su SQLSTATE
40P01— l'intera transazione, non solo lo statement fallito, con un piccolo backoff e un numero limitato di tentativi. - Dove possibile, comprimi il read-modify-write in un unico statement (
UPDATE ... SET x = x - 1 WHERE ...): un singolo statement acquisisce i suoi lock in un solo passaggio ed è molto più difficile che vada in deadlock.
