La proprecoj de la internaj mekanismoj de PostgreSQL permesas al ĝi esti tre rapida en iuj situacioj kaj "ne tre rapida" en aliaj. Hodiaŭ ni koncentriĝos pri klasika ekzemplo de konflikto inter kiel funkcias DBMS kaj kion faras la programisto per ĝi - Ĝisdatigo kontraŭ MVCC-principoj.
Mallonga rakonto de
Kiam vico estas modifita per UPDATE-komando, du operacioj efektive estas faritaj: DELETE kaj INSERT. EN aktuala versio de la ŝnuro xmax estas agordita egala al la nombro de la transakcio kiu faris la ĜISDATIGON. Tiam ĝi estas kreita nova versio la sama linio; ĝia xmin valoro koincidas kun la xmax valoro de la antaŭa versio.
Iom da tempo post kiam ĉi tiu transakcio estas kompletigita, la malnova aŭ nova versio, depende de COMMIT/ROOLBACK
, estos rekonita "mortintaj" (mortaj opoj) pasinte VACUUM
laŭ la tabelo kaj malbaris.
Sed ĉi tio ne okazos tuj, sed problemoj kun la "mortintoj" povas esti akiritaj tre rapide - kun ripetaj aŭ
#1: Mi Ŝatas Movi ĝin
Ni diru, ke via metodo funkcias pri komerca logiko, kaj subite ĝi rimarkas, ke necesus ĝisdatigi la X-kampon en iu registro:
UPDATE tbl SET X = <newX> WHERE pk = $1;
Tiam, dum la ekzekuto progresas, rezultas, ke la Y-kampo ankaŭ devus esti ĝisdatigita:
UPDATE tbl SET Y = <newY> WHERE pk = $1;
... kaj poste ankaŭ Z - kial perdi tempon per bagateloj?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;
Kiom da versioj de ĉi tiu disko ni nun havas en la datumbazo? Jes, 4 pecoj! El ĉi tiuj unu estas grava, kaj 3 devos esti purigitaj post vi per [aŭto]VACUUM.
Ne faru ĝin tiel! Uzu ĝisdatigante ĉiujn kampojn en unu peto — preskaŭ ĉiam la logiko de la metodo povas esti ŝanĝita jene:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;
#2: Uzo ESTAS DISTINTA DE, Luko!
Do, vi ankoraŭ volis
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;
Peto en proksimume ĉi tiu formo okazas sufiĉe ofte kaj preskaŭ ĉiam ne por plenigi malplenan novan kampon, sed por korekti iujn erarojn en la datumoj. Samtempe, ŝi mem la ĝusteco de ekzistantaj datumoj tute ne estas konsiderata — sed vane! Tio estas, la rekordo estas reverkita, eĉ se ĝi enhavis ĝuste tion, kion oni volis — sed kial? Ni riparu ĝin:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;
Multaj homoj ne konscias pri la ekzisto de tia mirinda operatoro, do jen trompo IS DISTINCT FROM
kaj aliaj logikaj operatoroj por helpi:
... kaj iom pri operacioj sur komplekso ROW()
-esprimoj:
#3: Mi rekonas mian amatinon per... blokado
estas lanĉitaj du identaj paralelaj procezoj, ĉiu el kiuj provas marki la eniron ke ĝi estas "en progreso":
UPDATE tbl SET processing = TRUE WHERE pk = $1;
Eĉ se ĉi tiuj procezoj efektive faras aferojn sendepende de unu la alian, sed ene de la sama identigilo, la dua kliento estos "ŝlosita" sur ĉi tiu peto ĝis la unua transakcio estas finita.
Solvo # 1: la tasko estas reduktita al la antaŭa
Ni simple aldonu ĝin denove IS DISTINCT FROM
:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;
En ĉi tiu formo, la dua peto simple ne ŝanĝos ion en la datumbazo, ĉio jam estas kiel ĝi devus esti - tial, blokado ne okazos. Poste, ni prilaboras la fakton "ne trovi" la rekordon en la aplikata algoritmo.
Solvo # 2: konsilaj seruroj
Granda temo por aparta artikolo, pri kiu vi povas legi
Solvo # 3: stultaj vokoj
Sed ĝuste ĉi tio devus okazi al vi samtempa laboro kun la sama rekordo? Aŭ ĉu vi fuŝis kun la algoritmoj por voki komercan logikon ĉe la klienta flanko, ekzemple? Kaj se vi pensas pri tio?...
fonto: www.habr.com