Penso che probabilmente intendevo aggiungere quel commento alla risposta precedente, su due affermazioni separate. È passato più di un anno fa, quindi non ne sono più sicuro.
La query basata su wCTE non risolve davvero il problema che dovrebbe, ma dopo averlo riesaminato più di un anno dopo non vedo la possibilità di perdere aggiornamenti nella versione di wCTE.
(Si noti che tutte queste soluzioni funzioneranno bene solo se si tenta di modificare esattamente una riga con ciascuna transazione. Non appena si tenta di apportare più modifiche in una transazione, le cose diventano disordinate a causa della necessità di ripetere i cicli sui rollback. dovresti usare un punto di salvataggio tra ogni modifica.)
Versione a due istruzioni soggetta ad aggiornamenti persi.
La versione che utilizza due separati prospetti è soggetta ad aggiornamenti persi meno che l'applicazione controlla il conteggio interessata fila dalla UPDATE
dichiarazione e la INSERT
dichiarazione e tentativi se entrambi sono pari a zero.
Immagina cosa succede se hai due transazioni in READ COMMITTED
isolamento.
- TX1 esegue il
UPDATE
(nessun effetto)
- TX1 esegue il
INSERT
(inserisce una riga)
- TX2 esegue il
UPDATE
(nessun effetto, la riga inserita da TX1 non è ancora visibile)
- TX1
COMMIT
s.
- TX2 esegue il simbolo
INSERT
*, che ottiene una nuova istantanea in grado di vedere la riga impegnata da TX1. La EXISTS
clausola restituisce true, poiché TX2 ora può vedere la riga inserita da TX1.
Quindi TX2 non ha alcun effetto. A meno che l'app non controlli il conteggio delle righe dall'aggiornamento e dall'inserimento e riprova se entrambi riportano zero righe, non saprà che la transazione non ha avuto alcun effetto e continuerà allegramente.
L'unico modo in cui può verificare i conteggi delle righe interessati è eseguirlo come due istruzioni separate anziché come una multiistruzione o utilizzare una procedura.
È possibile utilizzare l' SERIALIZABLE
isolamento, ma sarà comunque necessario un ciclo di tentativi per gestire gli errori di serializzazione.
La versione di wCTE protegge dal problema degli aggiornamenti persi perché INSERT
dipende dal fatto che UPDATE
influisca su qualsiasi riga, piuttosto che su una query separata.
Il wCTE non elimina violazioni uniche
La versione CTE scrivibile non è ancora un upsert affidabile.
Considera due transazioni che lo eseguono contemporaneamente.
Entrambi eseguono la clausola VALUES.
Ora entrambi eseguono la UPDATE
porzione. Poiché non vi sono righe corrispondenti alla UPDATE
clausola s where, entrambi restituiscono un gruppo di risultati vuoto dall'aggiornamento e non apportano modifiche.
Ora entrambi eseguono la INSERT
porzione. Poiché le UPDATE
righe zero restituite per entrambe le query, entrambe tentano di eseguire INSERT
la riga.
Uno ha successo. Si lancia una violazione unica e si interrompe.
Questo non è motivo di preoccupazione per la perdita di dati fintanto che l'app verifica la presenza di errori dalle sue query (ovvero qualsiasi app scritta in modo decente) e ci riprova, ma rende la soluzione non migliore delle versioni a due istruzioni esistenti. Non elimina la necessità di un ciclo di tentativi.
Il vantaggio offerto da wCTE rispetto alla versione a due istruzioni esistente è che utilizza l'output di UPDATE
per decidere se INSERT
, invece di utilizzare una query separata rispetto alla tabella. Questa è in parte un'ottimizzazione, ma in parte protegge da un problema con la versione a due istruzioni che causa la perdita di aggiornamenti; vedi sotto.
È possibile eseguire il wCTE in SERIALIZABLE
isolamento, ma si otterranno solo errori di serializzazione anziché violazioni univoche. Non cambierà la necessità di un ciclo di tentativi.
Il wCTE non sembra essere vulnerabile agli aggiornamenti persi
Il mio commento ha suggerito che questa soluzione potrebbe comportare la perdita di aggiornamenti, ma dopo aver esaminato che penso che potrei essermi sbagliato.
È passato più di un anno fa e non riesco a ricordare le circostanze esatte, ma penso che probabilmente mi sia sfuggito il fatto che gli indici univoci abbiano un'eccezione parziale alle regole di visibilità delle transazioni al fine di consentire a una transazione di inserimento di attendere che un'altra venga inserita o spostata indietro prima di procedere.
O forse mi sono perso il fatto che INSERT
in wCTE è subordinato alla presenza o meno di UPDATE
eventuali righe interessate, non alla presenza della riga candidata nella tabella.
I conflitti INSERT
in un indice univoco attendono il commit / rollback
Supponi che venga eseguita una copia della query, inserendo una riga. La modifica non è stata ancora impegnata. La nuova tupla esiste nell'heap e nell'indice univoco, ma non è ancora visibile ad altre transazioni, indipendentemente dai livelli di isolamento.
Ora viene eseguita un'altra copia della query. La riga inserita non è ancora visibile poiché la prima copia non è stata impegnata, quindi l'aggiornamento non corrisponde a nulla. La query continuerà a tentare un inserimento, che vedrà che un'altra transazione in corso sta inserendo la stessa chiave e bloccherà l'attesa o il rollback di tale transazione .
Se la prima transazione viene eseguita, la seconda fallirà con una violazione unica, come sopra. Se la prima transazione viene ripristinata, la seconda procederà invece al suo inserimento.
L' INSERT
essere dipendenti dal UPDATE
conteggio delle righe protegge dagli aggiornamenti persi
A differenza del caso a due dichiarazioni, non credo che il wCTE sia vulnerabile agli aggiornamenti persi.
Se UPDATE
non ha alcun effetto, INSERT
verrà sempre eseguito, poiché è strettamente subordinato al fatto che abbia UPDATE
fatto qualcosa, non allo stato della tabella esterna. Quindi può ancora fallire con una violazione unica, ma non può fallire silenziosamente per avere alcun effetto e perdere completamente l'aggiornamento.