Le peculiarità dei meccanismi interni di PostgreSQL gli permettono di essere molto veloce in alcune situazioni e “poco veloce” in altre. Oggi ci concentreremo su un classico esempio di conflitto tra il funzionamento di un DBMS e ciò che ne fa lo sviluppatore: AGGIORNAMENTO vs principi MVCC.
Breve storia da
Quando una riga viene modificata da un comando UPDATE, vengono effettivamente eseguite due operazioni: DELETE e INSERT. IN versione corrente della stringa xmax è impostato uguale al numero della transazione che ha eseguito l'AGGIORNAMENTO. Quindi viene creato nuova versione la stessa linea; il suo valore xmin coincide con il valore xmax della versione precedente.
Qualche tempo dopo il completamento di questa transazione, la versione vecchia o nuova, a seconda COMMIT/ROOLBACK
, verrà riconosciuto "morto" (tuple morte) al passaggio VACUUM
secondo la tabella e cancellato.
Ma questo non accadrà immediatamente, ma i problemi con i "morti" possono essere acquisiti molto rapidamente - con ripetuti o
# 1: Mi piace spostarlo
Supponiamo che il tuo metodo stia lavorando sulla logica aziendale e all'improvviso si rende conto che sarebbe necessario aggiornare il campo X in alcuni record:
UPDATE tbl SET X = <newX> WHERE pk = $1;
Quindi, man mano che l'esecuzione procede, risulta che anche il campo Y dovrebbe essere aggiornato:
UPDATE tbl SET Y = <newY> WHERE pk = $1;
... e poi anche Z - perché perdere tempo in sciocchezze?
UPDATE tbl SET Z = <newZ> WHERE pk = $1;
Quante versioni di questo record abbiamo ora nel database? Sì, 4 pezzi! Di questi, uno è rilevante e 3 dovranno essere ripuliti dopo di te da [auto]VACUUM.
Non farlo in questo modo! Utilizzo aggiornando tutti i campi in un'unica richiesta — quasi sempre la logica del metodo può essere modificata in questo modo:
UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;
#2: L'uso È DISTINTO DA, Luke!
Quindi, volevi ancora
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;
Una richiesta approssimativamente in questa forma avviene abbastanza spesso e quasi sempre non per compilare un nuovo campo vuoto, ma per correggere alcuni errori nei dati. Allo stesso tempo, lei stessa la correttezza dei dati esistenti non viene affatto presa in considerazione - ma invano! Cioè, il documento viene riscritto, anche se conteneva esattamente ciò che si voleva, ma perché? Risolviamolo:
UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;
Molte persone non sono a conoscenza dell'esistenza di un operatore così meraviglioso, quindi ecco un riassunto IS DISTINCT FROM
e altri operatori logici per aiutare:
... e un po' di operazioni su complesse ROW()
-espressioni:
#3: Riconosco la mia dolce metà... bloccando
vengono lanciati due processi paralleli identici, ognuno dei quali tenta di contrassegnare la voce che è “in corso”:
UPDATE tbl SET processing = TRUE WHERE pk = $1;
Anche se questi processi effettivamente eseguono operazioni indipendenti l’uno dall’altro, ma all’interno dello stesso ID, il secondo client verrà “bloccato” su questa richiesta fino al completamento della prima transazione.
Decisione n. 1: il compito si riduce a quello precedente
Aggiungiamolo di nuovo IS DISTINCT FROM
:
UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;
In questo modulo, la seconda richiesta semplicemente non cambierà nulla nel database, tutto è già come dovrebbe essere, quindi non si verificherà il blocco. Successivamente, elaboriamo il fatto di "non trovare" il record nell'algoritmo applicato.
Decisione n. 2: blocchi consultivi
Un argomento importante per un articolo separato, in cui puoi leggere
Decisione n. 3: chiamate stupide
Ma questo è esattamente ciò che dovrebbe accaderti lavoro simultaneo con lo stesso record? Oppure, ad esempio, hai sbagliato con gli algoritmi per richiamare la logica di business sul lato client? E se ci pensi?..
Fonte: habr.com