Leçons du module (3/4)
Verrouillage et SELECT FOR UPDATE
Comprendre comment fonctionne une base de données de manière isolée sur un seul terminal est simple... non attendez, je l'ai déjà dit !
Parfois, les « mises à jour atomiques » (par exemple stock = stock - 1) ne suffisent pas.
Imaginons que vous deviez vérifier si l'utilisateur dispose d'un solde suffisant pour acheter des produits d'une valeur de 50 € :
SELECT balance FROM users WHERE id = 1;- JS : Si le solde est < 50, bloquez-les !
- Sinon, procédez au paiement...
- Exécutez la déduction ! (Et ici vous trouvez un utilisateur qui, en validant le chèque à l'étape 2, en a lancé un autre parallèle qui a brûlé son compte et maintenant vous descendez en dessous de zéro !).
FOR UPDATE Verrouillage explicite
L'ajout de FOR UPDATE à la fin d'un SELECT transforme comme par magie votre lecture innocente en un verrou scellé contre d'autres clients jusqu'au prochain COMMIT. Personne d'autre ne pourra effectuer une MISE À JOUR (ou des lectures de mise à jour simultanées) sur ces lignes spécifiques sélectionnées jusqu'à ce que vous décidiez quoi en faire !
BEGIN;
-- If id=3 had already been locked by T1, T2 will sit in perpetual loading on this "read"
-- frozen in place, before receiving the raw data, until T1 runs COMMIT freeing everyone!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;
-- at this point our Node JS instance lives with the guarantee, knowing for certain that NO ONE ELSE in the world has
-- manipulated (nor been able to read with intent to alter) that balance while we complete the application logic
UPDATE users SET balance = balance - 50 WHERE id = 3;
COMMIT;Il existe même FOR SHARE qui permet aux autres lecteurs de continuer à lire (s'ils sont sur un verrou de base) sans bloquer les lectures d'état, mais en bloquant toute mise à jour ultérieure possible jusqu'à ce que vous vous engagez. Cependant, pour les modifications, utilisez toujours la force brute de la clause de verrouillage exclusif :
Essayez-le vous-même
Ouvrez une transaction normale à durée limitée et verrouillez exclusivement la lecture de l'ID de produit = 5. Exécutez une requête en deux étapes : 1. Ouvrez la transaction (BEGIN) 2. Interrogez « produits » en récupérant la ligne complète pour l'ID 5 avec la garantie que personne ne la modifiera, en attachant l'option « POUR MISE À JOUR ».
Afficher l'indice
BEGIN ;, nouvelle ligne, SELECT * FROM products WHERE id = 5 FOR UPDATE ;
Solution disponible après 3 tentatives
Parfois, la transaction se bloque pendant des périodes désastreuses (impasse ou attentes infinies). Pour éviter cela, vous pouvez ordonner à la base de données d'abandonner dès que la ligne n'est pas immédiatement disponible : en utilisant NOWAIT à la fin, ce qui provoquerait le rebond d'une exception du serveur SQL vers JS pour être interceptée sans se bloquer définitivement. Exécutez un SELECT pour la catégorie ID=1 et utilisez FOR UPDATE mais en appliquant NOWAIT.
Afficher l'indice
SELECT * FROM catégories WHERE id = 1 FOR UPDATE NOWAIT ;
Solution disponible après 3 tentatives