Particularitățile mecanismelor interne ale PostgreSQL îi permit să fie foarte rapid în unele situații și „nu foarte rapid” în altele. Astăzi ne vom concentra pe un exemplu clasic de conflict între modul în care funcționează un DBMS și ceea ce face dezvoltatorul cu el - UPDATE vs principiile MVCC.
Scurtă poveste de la
Când un rând este modificat printr-o comandă UPDATE, sunt efectiv efectuate două operații: DELETE și INSERT. ÎN versiunea curentă a șirului xmax este setat egal cu numărul tranzacției care a efectuat UPDATE. Apoi este creat o noua versiune aceeași linie; valoarea sa xmin coincide cu valoarea xmax a versiunii anterioare.
La ceva timp după finalizarea acestei tranzacții, versiunea veche sau nouă, în funcție de COMMIT/ROOLBACK
, vor fi recunoscute „mort” (tupluri morți) la trecere VACUUM
conform tabelului și șters.
Dar acest lucru nu se va întâmpla imediat, dar problemele cu „morții” pot fi dobândite foarte repede - cu repetate sau
#1: Îmi place să-l mut
Să presupunem că metoda dvs. funcționează pe logica afacerii și dintr-o dată își dă seama că ar fi necesar să actualizați câmpul X într-o înregistrare:
UPDATE tbl SET X = <newX> WHERE pk = $1;
Apoi, pe măsură ce execuția progresează, se dovedește că și câmpul Y ar trebui actualizat:
UPDATE tbl SET Y = <newY> WHERE pk = $1;
... și apoi și Z - de ce să pierdeți timpul cu fleacuri?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;
Câte versiuni ale acestei înregistrări avem acum în baza de date? Da, 4 bucăți! Dintre acestea, una este relevantă, iar 3 vor trebui curățate după tine prin [auto]VACUUM.
Nu face acest lucru! Utilizare actualizarea tuturor câmpurilor într-o singură solicitare — aproape întotdeauna logica metodei poate fi schimbată astfel:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;
#2: Utilizarea ESTE DIFERENT DE, Luke!
Deci, încă ai vrut
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;
O solicitare în aproximativ acest formular apare destul de des și aproape întotdeauna nu pentru a completa un câmp nou gol, ci pentru a corecta unele erori în date. În același timp, ea însăși corectitudinea datelor existente nu este luată în considerare deloc - dar în zadar! Adică înregistrarea este rescrisă, chiar dacă conținea exact ceea ce se dorea – dar de ce? Hai să o reparăm:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;
Mulți oameni nu sunt conștienți de existența unui astfel de operator minunat, așa că iată o foaie de cheat IS DISTINCT FROM
și alți operatori logici pentru a ajuta:
... și puțin despre operațiunile pe complex ROW()
-expresii:
#3: Îmi recunosc iubita prin... blocare
sunt lansate două procese paralele identice, dintre care fiecare încearcă să marcheze intrarea că este „în desfășurare”:
UPDATE tbl SET processing = TRUE WHERE pk = $1;
Chiar dacă aceste procese realizează lucruri independente unele de altele, dar în cadrul aceluiași ID, al doilea client va fi „blocat” la această solicitare până la finalizarea primei tranzacții.
Soluția #1: sarcina se reduce la cea anterioară
Să-l adăugăm din nou IS DISTINCT FROM
:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;
În această formă, a doua solicitare pur și simplu nu va schimba nimic în baza de date, totul este deja așa cum ar trebui să fie - prin urmare, blocarea nu va avea loc. În continuare, procesăm faptul de a „nu găsi” înregistrarea în algoritmul aplicat.
Soluția #2: încuietori de consiliere
Un subiect mare pentru un articol separat, în care puteți citi despre
Soluția #3: apeluri stupide
Dar asta este exact ceea ce ar trebui să vi se întâmple lucru simultan cu aceeași înregistrare? Sau ai greșit cu algoritmii pentru apelarea logicii de afaceri din partea clientului, de exemplu? Și dacă te gândești bine?...
Sursa: www.habr.com