Lecții din modul (2/4)
Condiții de cursă
Înțelegerea modului în care funcționează o bază de date izolată pe un singur terminal este simplă, dar într-un scenariu web real vor exista sute de procese simultane „Node/PHP/Go” care trimit cereri. Aici se nasc faimoasele Condiții de cursă.
Problema „Actualizare pierdută”.
Actualizarea pierdută se întâmplă atunci când două fire citesc același rând vechi, fac niște calcule pe el pe serverul JavaScript și apoi trimit înapoi UPDATE pentru a salva rezultatul.
Scenariu:
- Produsul #5 are 10 unități.
- Utilizatorul A cumpără unul (Serverul A citește „10”, calculează „9”).
- În aceeași milisecundă utilizatorul B cumpără altul înainte ca achiziția lui A să termine faza COMMIT.
- Serverul B citește și „10”, calculează „9”.
- Serverul A trimite
UPDATE: set quantity = 9. - Serverul B trimite
UPDATE: set quantity = 9. - Ambii cred că au scăzut numărul cu succes. Dar valoarea finală ar fi trebuit să fie de 8 unități! O achiziție fantomă a fost pierdută.
Soluție fără tranzacții grele (Actualizări atomice)
În loc să citiți și să faceți calcule afară, lăsați DB să o facă în timpul actualizării în sine, care este întotdeauna sincronizată.
-- Wrong
UPDATE products SET stock = 9 WHERE id = 5;
-- Correct and natively thread-safe
UPDATE products SET stock = stock - 1 WHERE id = 5;Mai multe anomalii extreme (Dirty Reads & Phantoms)
- Dirty Read: ați citit datele temporare ale altcuiva care nu au fost încă confirmate de un COMMIT (și ar putea trece brusc la ROLLBACK, invalidându-vă).
- Citituri fantomă: Faceți o scanare a tuturor rândurilor, între timp un al doilea thread inserează o nouă înregistrare, apoi faceți o altă scanare în timpul aceleiași tranzacții și descoperiți ca magic că noua înregistrare a apărut de nicăieri, compromițând rapoartele financiare calculate.
Modificați atomic înregistrarea clienților pentru o manipulare mică a șirurilor.\nSă presupunem că vrem să redenumim „orașul” unui client pentru a adăuga un asterisc. \nFă-o prin valorificarea propriei rânduri pentru cea cu id=10, concatenând „- Old” la valoarea curentă (Utilizați postgres' || conducta de concatenare).
Afișează indiciu
UPDATE clienții SET oraș = oraș || ' - Vechi' WHERE id = 10;
Soluție disponibilă după 3 încercări
O actualizare clasică pierdută are loc în plăți și prețuri.\nSă presupunem că există o creștere de urgență a prețului: creșteți „unit_price” din tabelul „order_items” (pentru produsul 1 din comanda 1) cu 20% atomic, adică înmulțindu-l * 1,2 în loc să inserați manual noul preț JS precomp.
Afișează indiciu
UPDATE articole_comandă SET preț_unitate = preț_unitate * 1.2 WHERE cod_comandă = 1 ȘI cod_produs = 1;
Soluție disponibilă după 3 încercări