Lektionen des Moduls (3/4)
Locking und SELECT FOR UPDATE
Es ist einfach zu verstehen, wie eine Datenbank isoliert auf einem einzelnen Terminal funktioniert ... Nein, Moment, das habe ich bereits gesagt!
Manchmal reichen „Atomic Updates“ (z. B. stock = stock - 1) nicht aus.
Stellen wir uns vor, Sie müssen prüfen, ob der Benutzer über genügend Guthaben verfügt, um Waren im Wert von 50 € zu kaufen:
SELECT balance FROM users WHERE id = 1;- JS: Wenn der Kontostand < 50 ist, blockieren Sie sie!
- Andernfalls fahren Sie mit dem Bezahlen fort...
- Führen Sie den Abzug durch! (Und hier finden Sie einen Benutzer, der bei der Validierung des Schecks in Schritt 2 einen weiteren parallelen Scheck ausgelöst hat, der sein Konto durchgebrannt hat, und jetzt sinken Sie unter Null!).
FOR UPDATE Explizite Sperre
Das Hinzufügen von FOR UPDATE am Ende eines SELECT verwandelt Ihren unschuldigen Lesevorgang auf magische Weise in eine versiegelte Sperre gegen andere Clients bis zum nächsten COMMIT. Niemand sonst kann UPDATE (oder gleichzeitige Lesevorgänge zur Aktualisierung) für diese bestimmten ausgewählten Zeilen durchführen, bis Sie entscheiden, was mit ihnen geschehen soll!
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;Es gibt sogar FOR SHARE, der es anderen Lesern ermöglicht, weiterzulesen (bei einer Basissperre), ohne Statuslesevorgänge zu blockieren, aber jede mögliche nachfolgende Aktualisierung blockiert, bis Sie einen Commit durchführen. Für Änderungen nutzen Sie jedoch immer die rohe Gewalt der exklusiven Sperrklausel:
Probieren Sie es selbst aus
Öffnen Sie eine normale zeitbezogene Transaktion und sperren Sie ausschließlich das Lesen der Produkt-ID=5. Führen Sie eine zweistufige Abfrage aus: 1. Öffnen Sie die Transaktion (BEGIN) 2. Fragen Sie „Produkte“ ab und rufen Sie die vollständige Zeile für ID 5 ab, mit der Garantie, dass niemand sie ändern wird, indem Sie die Option „FOR UPDATE“ anhängen.
Hinweis anzeigen
BEGIN;, newline, SELECT * FROM products WHERE id = 5 FOR UPDATE;
Lösung nach 3 Versuchen verfügbar
Manchmal hängt die Transaktion für katastrophale Zeiträume (Deadlock oder unendliche Wartezeiten). Um dies zu verhindern, können Sie der Datenbank befehlen, den Vorgang aufzugeben, sobald die Zeile nicht sofort verfügbar ist: Verwenden Sie NOWAIT am Ende, was dazu führen würde, dass eine SQL Server-Ausnahme an JS zurückspringt, um sie abzufangen, ohne für immer zu hängen. Führen Sie ein SELECT für Kategorie-ID=1 aus und verwenden Sie FOR UPDATE, erzwingen Sie jedoch NOWAIT.
Hinweis anzeigen
SELECT * FROM groups WHERE id = 1 FOR UPDATE NOWAIT;
Lösung nach 3 Versuchen verfügbar