Lezioni del modulo (3/4)
Locking e SELECT FOR UPDATE
Capire come funziona un database in isolamento su un singolo terminale è semplice... no aspetta, questo l'ho già detto!
A volte gli "Atomic Updates" (es. stock = stock - 1) non bastano.
Immaginiamo che devi controllare se l'utente ha saldo sufficiente per comprare 50€ di roba:
SELECT balance FROM users WHERE id = 1;- JS: Se il balance è < 50, bloccatelo!
- Altrimenti procedi con checkout...
- Esegui la detrazione! (E qui ti trovi con un utente che mentre validava il controllo al punto 2 ne ha lanciato un altro parallelo che gli ha bruciato il conto e mo scendi sotto zero!).
FOR UPDATE Explicit Locking
Aggiungere FOR UPDATE in fondo ad un SELECT trasforma magicamente la tua innocente lettura in un lucchetto (Lock) blindato per gli altri client fino al prossimo COMMIT. Nessun altro potra' fare UPDATE (o read for updates concorrenti) su quelle specifiche righe selezionate finchè tu non decidi cosa fartene!
BEGIN;
-- Se id=3 era già stato bloccato da T1, T2 rimarrà in perenne caricamento su questa "lettura"
-- fermo immobile, prima di ricevere il dato grezzo, finchè T1 non esegue il COMMIT liberando tutti!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;
-- a questo punto la nostra istanza JS Node vive garantita sapendo per certo che NESSUN ALTRO al mondo ha
-- manipolato (nè potuto leggere a scopo di alterare) quel saldo mentre completiamo la logica applicativa
UPDATE users SET balance = balance - 50 WHERE id = 3;
COMMIT;Esiste perfino il FOR SHARE che lascia la possibilità agli altri lettori di continuare a leggere (se in base lock) senza impedire la lettura di stato ma bloccando però qualsiasi possibile update successivo finché non commiti. Per le modifiche però usa sempre la forza bruta della clusola lockante esclusiva:
Prova tu
Apri una normale transazione temporale e blocca in maniera esclusiva la lettura del prodotto ID=5.\nEsegui una query in due step:\n1. Apri la transazione (BEGIN)\n2. Interroga i 'products' pescando l'intera riga per l'ID 5 con la garanzia che nessuno lo alteri, attaccandogli quindi l'opzione 'FOR UPDATE'.
Mostra suggerimento
BEGIN;, Vai a capo, SELECT * FROM products WHERE id = 5 FOR UPDATE;
Soluzione disponibile dopo 3 tentativi
Alcune volte la transazione si blocca per periodi disastrosi (Deadlock o wait infiti).\nPer impedirlo puoi comandare al database di rinunciare nel momento in cui la linea non fosse immediatamente reperita: usando NOWAIT alla chiusura che causerebbe quindi in SQL server exception da rispedire al JS da try-catchare senza appenderlo a vita.\nFai una SELECT per la categoria ID=1 ed impiega un FOR UPDATE ma imponendo NOWAIT.
Mostra suggerimento
SELECT * FROM categories WHERE id = 1 FOR UPDATE NOWAIT;
Soluzione disponibile dopo 3 tentativi