Le caratteristiche del funzionamento dei meccanismi interni di PostgreSQL lo rendono molto veloce in alcune situazioni e «non molto» in altre. Oggi ci concentreremo su un esempio classico del conflitto tra come funziona un DBMS e ciò che fa lo sviluppatore con esso — UPDATE vs principi MVCC.
Brevemente, la trama di :
Quando una riga viene modificata con il comando UPDATE, in realtà vengono eseguite due operazioni: DELETE e INSERT. Nella versione attuale della riga viene impostato xmax, pari al numero della transazione che ha eseguito l'UPDATE. Poi viene creata una una nuova versione stessa riga; il valore xmin corrisponde al valore xmax della versione precedente.
Dopo un certo periodo, dopo il completamento di questa transazione, la versione vecchia o nuova, a seconda di COMMIT/ROLLBACK, verrà riconosciuta come «tuples morte» (dead tuples) nel corso della VACUUM scansione della tabella e verranno rimosse.

Ma questo non accadrà immediatamente, mentre i problemi con i «morti» possono sorgere molto rapidamente — durante aggiornamenti ripetuti o in una grande tabella, e poco dopo ci si può trovare nella situazione in cui anche .
#1: I Like To Move It
Supponiamo che il tuo metodo sulla logica di business funzioni normalmente, e improvvisamente realizzi che sarebbe necessario aggiornare il campo X in un certo record:
UPDATE tbl SET X = WHERE pk = $1;Successivamente, durante l'esecuzione, si scopre che anche il campo Y dovrebbe essere aggiornato:
UPDATE tbl SET Y = WHERE pk = $1;… e poi anche Z — perché farsi degli scrupoli?
UPDATE tbl SET Z = WHERE pk = $1;Quante versioni di questa registrazione abbiamo ora nel database? Ah, 4 pezzi! Di cui una attuale e 3 dovranno essere rimosse da voi [auto]VACUUM.
Non fatelo! Utilizzate l'aggiornamento di tutti i campi con una sola query — quasi sempre la logica del metodo può essere modificata in questo modo:
UPDATE tbl SET X = , Y = , Z = WHERE pk = $1;#2: Use IS DISTINCT FROM, Luke!
Quindi, avete davvero voglia di (durante l'applicazione di uno script o di un convertitore, per esempio). E nello script c'è qualcosa di simile:
UPDATE tbl SET X = WHERE pk BETWEEN $1 AND $2;Circa in questa forma, la query si incontra abbastanza spesso e quasi sempre non per riempire un nuovo campo vuoto, ma per correggere alcuni errori nei dati. A tal proposito, la correttezza dei dati già esistenti non viene affatto considerata — e sarebbe un peccato! Quindi la registrazione viene riscritta, anche se conteneva esattamente ciò che si desiderava — ma 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 un operatore così fantastico, quindi ecco una guida per IS DISTINCT FROM e altri operatori logici a supporto:

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

#3: А я милого узнаю по… блокировке
Vengono avviati due processi paralleli identici, ognuno dei quali cerca di contrassegnare la registrazione come "in lavorazione":
UPDATE tbl SET processing = TRUE WHERE pk = $1;Anche se questi processi eseguono cose indipendenti tra loro, nel contesto di un ID, in questa richiesta il secondo cliente verrà "bloccato" fino al termine della prima transazione.
Soluzione n. 1: il problema è ridotto al precedente
Basta aggiungere nuovamente IS DISTINCT FROM:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;In questo modo, la seconda richiesta non cambierà nulla nel database, dato che è già "tutto a posto" — pertanto non si verificherà alcun blocco. Successivamente, il fatto che la registrazione non venga trovata viene gestito nell'algoritmo applicativo.
Soluzione n. 2: advisory locks
Un argomento vasto per un articolo separato, dove si può leggere su .
Soluzione n. 3: chiamate senza [d]ubbio
È certo che dovrebbe succedere da parte vostra lavoro simultaneo su un unico record? Или вы все-таки накосячили с алгоритмами вызовов бизнес-логики со стороны клиента, например? А если подумать?..
Fonte: habr.com
