Erreur 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"
Deux transactions (ou plus) attendent chacune un verrou détenu par l'autre. Aucune ne pourra jamais progresser, donc PostgreSQL détecte le cycle, choisit une transaction comme victime et l'annule avec cette erreur. L'autre transaction se poursuit normalement.
Ce que signifie cette erreur
Les verrous au niveau ligne dans PostgreSQL sont conservés jusqu'à ce que la transaction valide (commit) ou annule (rollback). Si la transaction A verrouille la ligne 1 puis veut la ligne 2, tandis que la transaction B détient la ligne 2 et veut la ligne 1, elles forment un cycle : un interblocage (deadlock). Lorsqu'un backend attend un verrou depuis plus longtemps que deadlock_timeout (par défaut : 1 seconde), PostgreSQL lance une vérification d'interblocage ; s'il trouve un cycle, il annule l'un des participants avec le SQLSTATE 40P01, cousin du 40001.
Un cadrage important : la base de données a déjà résolu la situation au moment où vous voyez l'erreur. Rien n'est corrompu et la transaction survivante a terminé son attente de verrou. Ce qui reste est un problème applicatif — la transaction annulée a subi un rollback et son travail est perdu, donc votre application doit être prête à la réessayer.
Causes fréquentes
- Mettre à jour les mêmes lignes dans des ordres différents. Le cas classique : le job A met à jour le compte 1 puis le compte 2, le job B met à jour le compte 2 puis le compte 1. Deux processus d'écriture multi-lignes sans ordre convenu peuvent s'interbloquer.
- Accéder aux tables dans des ordres différents selon les chemins de code — par ex. un chemin écrit
orderspuisinventory, un autre écritinventorypuisorders. - Les clés étrangères. Insérer ou mettre à jour une ligne enfant pose un verrou partagé sur la ligne parente référencée. Mêlé à des mises à jour concurrentes du parent, cela produit des interblocages qui semblent mystérieux car aucune requête ne mentionne les deux tables.
- Les montées en gamme de verrous : lire une ligne avec
SELECT ... FOR SHAREpuis la mettre à jour permet à deux transactions d'acquérir le verrou partagé, puis de se bloquer mutuellement lors de la montée en gamme. - Les transactions longues ne provoquent pas d'interblocages à elles seules, mais elles conservent les verrous plus longtemps et élargissent la fenêtre pour chacun des schémas ci-dessus.
Comment la diagnostiquer
L'erreur elle-même contient l'essentiel de ce dont vous avez besoin :
DETAILliste les identifiants de processus et la transaction que chacun attendait.CONTEXTnomme souvent le tuple et la relation en cours d'écriture.- Le journal du serveur à cet horodatage enregistre les requêtes impliquées — c'est ce que pointe le HINT.
Activez log_lock_waits = on pour journaliser aussi toute attente de verrou plus longue que deadlock_timeout, ce qui vous montre les quasi-collisions, pas seulement les collisions. Pour une investigation en direct de qui bloque qui :
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));
Comment la corriger
- Convenez d'un ordre global de verrouillage. Lorsqu'une transaction écrit plusieurs lignes, acquérez d'abord les verrous dans un ordre déterministe :
SELECT id FROM accounts WHERE id IN (7, 3, 42) ORDER BY id FOR UPDATE; -- puis les UPDATE, dans n'importe quel ordre - Accédez aux tables dans le même ordre partout (documentez l'ordre ; peu importe lequel, seule compte la cohérence).
- Gardez les transactions courtes. N'attendez jamais une saisie utilisateur, un appel HTTP ou une file d'attente en détenant des verrous de ligne.
- Réessayez sur le SQLSTATE
40P01— toute la transaction, pas seulement l'instruction en échec, avec un petit backoff et un nombre borné de tentatives. - Quand c'est possible, réduisez le lire-modifier-écrire à une seule instruction (
UPDATE ... SET x = x - 1 WHERE ...) : une seule instruction acquiert ses verrous en une passe et s'interbloque bien plus difficilement.
