Perché CTE è aperto agli aggiornamenti persi?


8

Non capisco cosa volesse dire Craig Ringer quando commentò:

Questa soluzione è soggetta a aggiornamenti persi se la transazione di inserimento viene ripristinata; non esiste alcun controllo per imporre che UPDATE abbia interessato qualsiasi riga.

su https://stackoverflow.com/a/8702291/14731 . Fornisci una sequenza di eventi di esempio (ad es. Thread 1 fa X, Thread 2 fa Y) che dimostri come potrebbero verificarsi aggiornamenti persi.


1
Chiedimi qualcosa su un commento che ho lasciato oltre un anno fa su un argomento complesso ... divertente! Ora devo ricordare quale fosse esattamente il problema. Riesaminandolo ora.
Craig Ringer,

Risposte:


14

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 UPDATEdichiarazione e la INSERTdichiarazione e tentativi se entrambi sono pari a zero.

Immagina cosa succede se hai due transazioni in READ COMMITTEDisolamento.

  • 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 COMMITs.
  • TX2 esegue il simbolo INSERT*, che ottiene una nuova istantanea in grado di vedere la riga impegnata da TX1. La EXISTSclausola 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' SERIALIZABLEisolamento, 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é INSERTdipende dal fatto che UPDATEinfluisca 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 UPDATEporzione. Poiché non vi sono righe corrispondenti alla UPDATEclausola s where, entrambi restituiscono un gruppo di risultati vuoto dall'aggiornamento e non apportano modifiche.

  • Ora entrambi eseguono la INSERTporzione. Poiché le UPDATErighe zero restituite per entrambe le query, entrambe tentano di eseguire INSERTla 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 UPDATEper 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 SERIALIZABLEisolamento, 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 INSERTin wCTE è subordinato alla presenza o meno di UPDATEeventuali righe interessate, non alla presenza della riga candidata nella tabella.

I conflitti INSERTin 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' INSERTessere dipendenti dal UPDATEconteggio delle righe protegge dagli aggiornamenti persi

A differenza del caso a due dichiarazioni, non credo che il wCTE sia vulnerabile agli aggiornamenti persi.

Se UPDATEnon ha alcun effetto, INSERTverrà sempre eseguito, poiché è strettamente subordinato al fatto che abbia UPDATEfatto 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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.