Le caratteristiche del funzionamento dei meccanismi interni di PostgreSQL gli consentono di essere molto veloce in alcune situazioni e "non molto" in altre. Oggi ci concentreremo su un classico esempio di conflitto tra il modo in cui funziona un DBMS e ciò che fa con esso lo sviluppatore — UPDATE vs principi MVCC.
Brevemente il racconto di :
Quando una riga viene modificata con il comando UPDATE, vengono effettivamente eseguite due operazioni: DELETE e INSERT. In versione corrente della riga viene impostato xmax, uguale al numero della transazione che ha eseguito l'UPDATE. Viene quindi creata nuova versione la stessa riga; il valore xmin di essa coincide con il valore xmax della versione precedente.
Dopo un certo tempo, al termine di questa transazione, la vecchia o nuova versione, a seconda di COMMIT/ROLLBACK, verranno riconosciute "morte" (dead tuples) durante il passaggio VACUUM attraverso la tabella e ripulite.

Ma questo non accadrà immediatamente, mentre i problemi con i "morti" possono insorgere molto rapidamente — durante aggiornamenti ripetuti o in una grande tabella, e poco dopo ci si può trovare nella situazione in cui neanche .
#1: I Like To Move It
Supponiamo che il tuo metodo basato sulla logica di business stia funzionando, e all'improvviso si rende conto che dovrebbe aggiornare il campo X in un certo record:
UPDATE tbl SET X = WHERE pk = $1;Poi, nel corso dell'esecuzione, scopre che dovrebbe aggiornare anche il campo Y:
UPDATE tbl SET Y = WHERE pk = $1;… e poi anche Z — perché risparmiare?
UPDATE tbl SET Z = WHERE pk = $1;Quante versioni di questo record abbiamo ora nel database? Ah, 4 pezzi! Di cui una attuale, mentre 3 dovranno essere ripuliti da te con [auto]VACUUM.
Non farlo! Usa l'aggiornamento di tutti i campi in un'unica query — quasi sempre la logica di funzionamento del metodo può essere cambiata in questo modo:
UPDATE tbl SET X = , Y = , Z = WHERE pk = $1;#2: Use IS DISTINCT FROM, Luke!
Quindi, ti sei davvero deciso a (durante l'esecuzione di uno script o un convertitore, ad esempio). E nello script appare qualcosa del tipo:
UPDATE tbl SET X = WHERE pk BETWEEN $1 AND $2;In una forma simile, la query si incontra abbastanza spesso e quasi sempre non per riempire un nuovo campo vuoto, ma per correggere alcuni errori nei dati. In questo caso, la correttezza dei dati già esistenti non viene affatto considerata — a sproposito! Vale a dire che il record viene riscritto, anche se c'era esattamente ciò che si desiderava — e perché? Correggiamo:
UPDATE tbl SET X = WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM ; Molti non sono a conoscenza dell'esistenza di questo straordinario operatore, quindi ecco una guida su IS DISTINCT FROM e altri operatori logici per aiutarti:

… e un po' sulle operazioni con espressioni ROW()-complesse:

#3: А я милого узнаю по… блокировке
Vengono avviati due processi paralleli identici, ognuno dei quali cerca di segnare il record come "in lavorazione":
UPDATE tbl SET processing = TRUE WHERE pk = $1;Anche se questi processi svolgono effettivamente cose indipendenti l'uno dall'altro, all'interno di un unico ID, su questa richiesta il secondo client "si bloccherà" fino a quando non termina la prima transazione.
Soluzione nº1: il problema è stato ridotto al precedente
Semplicemente aggiungiamo di nuovo IS DISTINCT FROM:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;In questo modo, la seconda richiesta semplicemente non apporterà modifiche nel database, dove già "è tutto a posto" — quindi non si verificherà alcun blocco. In seguito, il fatto che il record "non si trovi" verrà già gestito nell'algoritmo applicativo.
Soluzione nº2: blocchi advisory
Un grande tema per un articolo a parte, dove si può leggere su .
Soluzione nº3: senza [d]ubbio delle chiamate
Ecco, esattamente, dovrebbe verificarsi la tua lavorazione simultanea dello stesso record? Или вы все-таки накосячили с алгоритмами вызовов бизнес-логики со стороны клиента, например? А если подумать?..
Fonte: habr.com
