Les transactions dans PostgreSQL assurent l'isolation — c'est une des caractéristiques clés d'ACID. Pour ça, le système utilise des verrous. Un verrou, c'est un mécanisme qui garantit que plusieurs transactions ne vont pas "se battre" pour la même ligne ou la même table. Imagine une file d'attente au supermarché : à la caisse, il n'y a qu'une personne à la fois. Les verrous, c'est pareil, mais pour contrôler l'accès aux données dans les tables.
Les principaux types de verrous
Dans PostgreSQL, il existe plusieurs types de verrous, certains que tu as peut-être déjà croisés dans EXPLAIN ANALYZE :
ROW EXCLUSIVE(verrou exclusif sur une ligne) — apparaît quand tu modifies des données dans une ligne. C'est le type de verrou le plus courant, par exemple quand tu fais unINSERT,UPDATEouDELETE.SHARE(verrou partagé) — utilisé pour les opérations qui garantissent que les données ne vont pas changer pendant l'exécution de la requête, par exemple avecSELECT ... FOR SHARE.EXCLUSIVE(verrou exclusif) — empêche toute autre opération sur les données, sauf la lecture.
On peut dire que les verrous, c'est un peu comme des chiens de garde qui protègent nos données.
Problème des verrous : qu'est-ce qui peut mal tourner ?
Maintenant qu'on pige ce que sont les verrous, voyons quels soucis ils peuvent causer. Le plus gros, c'est le problème de deadlock. Un deadlock (ou blocage mutuel) — c'est une situation où deux (ou plus) transactions attendent l'une l'autre, ce qui fait que tout reste bloqué dans une boucle infinie. Typiquement, ça ressemble à ça :
- Transaction 1 verrouille la ligne A et veut accéder à la ligne B.
- Transaction 2 verrouille la ligne B et veut accéder à la ligne A.
- Comme les deux lignes sont verrouillées, aucune transaction ne peut finir.
Exemple de deadlock
Pour ressentir la douleur d'un dev qui tombe sur un deadlock, imagine ce scénario :
-- Transaction 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- En même temps
-- Transaction 2
BEGIN;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
-- Transaction 1 essaie de verrouiller id = 2
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
-- Transaction 2 essaie de verrouiller id = 1
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
Et voilà ! Chaque transaction est bloquée par l'autre. PostgreSQL finit par détecter ça et balance une erreur de deadlock.
Comment éviter les deadlocks ?
- Ordre d'accès aux données cohérent.
Essaie toujours d'accéder aux données dans le même ordre. Si la transaction 1 verrouille d'abord la ligne A puis la ligne B, la transaction 2 doit suivre la même logique : d'abord la ligne A, puis la ligne B.
-- Bon ordre d'accès
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 200 WHERE id = 2;
COMMIT;
Minimise la durée des transactions.
Des transactions longues augmentent le risque de deadlock. Termine tes transactions le plus vite possible :BEGIN, fais toutes les modifs nécessaires et directCOMMIT.Utilise les niveaux d'isolation des transactions.
Si ton cas n'a pas besoin d'une isolation ultra-stricte, pense à utiliser le niveau d'isolationREAD COMMITTED. Ça permet d'éviter certains verrous.
Timeouts et diagnostic des verrous
Il existe des outils pour diagnostiquer et prévenir les verrous dans PostgreSQL. Par exemple, tu peux définir un temps d'attente max pour un verrou :
SET lock_timeout = '5s'; -- On met un timeout de verrou à 5 secondes
Si le verrou reste plus longtemps que ce temps, la transaction sera interrompue, ce qui évite un blocage total.
Surveiller les verrous dans PostgreSQL
Une commande bien pratique pour monitorer les verrous, c'est pg_locks. Elle affiche tous les verrous actifs dans le système :
SELECT * FROM pg_locks;
Tu peux voir quelles transactions tiennent des verrous et lesquelles attendent qu'ils soient libérés. Super utile pour déboguer les deadlocks.
Particularités de LOCK et verrouillage manuel des objets
Si tu veux gérer les verrous à la main, utilise la commande LOCK :
LOCK TABLE orders IN ACCESS EXCLUSIVE MODE;
Attention : ACCESS EXCLUSIVE — c'est le verrou le plus costaud, il interdit toute autre opération sur la table, même SELECT. Utilise-le seulement dans des cas spéciaux (genre modifier la structure de la table).
Pour verrouiller des lignes précises lors d'une modif, utilise SELECT ... FOR UPDATE :
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
Ça garantit qu'aucune autre transaction ne pourra modifier ces lignes avant la fin de ta transaction.
Ce qu'il faut retenir sur les verrous
Les verrous, c'est pas le mal, c'est un outil. Ils servent à garder l'intégrité des données, mais faut les manipuler avec soin. Voilà quelques conseils clés :
- Ne commence pas une transaction si tu sais pas exactement pourquoi tu la fais.
- Termine ta transaction le plus vite possible.
- Évite d'accéder aux données dans un ordre différent à chaque fois.
- Utilise le niveau d'isolation adapté à ton besoin.
Maintenant, t'es prêt à bosser avec les verrous et les deadlocks ! Que pg_locks et la sagesse d'ACID soient avec toi.
GO TO FULL VERSION