Gestione della concorrenza quando si utilizza il modello SELECT-UPDATE


25

Supponiamo che tu abbia il seguente codice (ignora che è terribile):

BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else

A mio avviso, questo NON gestisce correttamente la concorrenza. Solo perché hai una transazione non significa che qualcun altro non leggerà lo stesso valore che hai fatto prima di arrivare alla tua dichiarazione di aggiornamento.

Ora, lasciando il codice così com'è (mi rendo conto che questo è meglio gestito come una singola istruzione o anche meglio usando una colonna di autoincremento / identità) quali sono i modi sicuri per farlo gestire correttamente la concorrenza e prevenire le condizioni di competizione che consentono a due clienti di ottenere lo stesso valore id?

Sono abbastanza sicuro che l'aggiunta di a WITH (UPDLOCK, HOLDLOCK)a SELECT farà il trucco. Il livello di isolamento della transazione SERIALIZZABILE sembra funzionare anche poiché nega a chiunque altro di leggere ciò che hai fatto fino alla fine del tran ( AGGIORNAMENTO : questo è falso. Vedi la risposta di Martin). È vero? Funzioneranno entrambi allo stesso modo? L'uno è preferito all'altro?

Immagina di fare qualcosa di più legittimo di un aggiornamento ID: alcuni calcoli basati su una lettura che devi aggiornare. Potrebbero esserci molte tabelle coinvolte, alcune delle quali scriverai e altre no. Qual è la migliore pratica qui?

Dopo aver scritto questa domanda, penso che i suggerimenti per il blocco siano migliori perché in questo modo stai solo bloccando le tabelle di cui hai bisogno, ma apprezzerei l'input di chiunque.

PS E no, non conosco la risposta migliore e voglio davvero capire meglio! :)


Solo per chiarimenti: vuoi impedire a 2 client di leggere lo stesso valore o di emettere updateche potrebbero essere basati su dati obsoleti? In quest'ultimo caso, puoi utilizzare la rowversioncolonna per verificare se la riga da aggiornare non è stata modificata da quando è stata letta.
a1ex07,

Non vogliamo che un secondo client ottenga il vecchio valore id prima che venga aggiornato al nuovo valore dal primo client. Dovrebbe bloccare.
ErikE,

Risposte:


11

Basta affrontare l' SERIALIZABLEaspetto del livello di isolamento. Sì, questo funzionerà ma con rischio di deadlock.

Due transazioni saranno entrambe in grado di leggere la riga contemporaneamente. Non si bloccheranno a vicenda in quanto accetteranno un Sblocco di oggetti o RangeS-Sblocchi di indici dipendenti dalla struttura della tabella e questi blocchi sono compatibili . Ma si bloccheranno a vicenda quando tenteranno di acquisire i blocchi necessari per l'aggiornamento ( rispettivamente IXblocco oggetto o indice RangeS-U) che porteranno a deadlock.

L'uso di un UPDLOCKsuggerimento esplicito invece serializzerà le letture evitando così il rischio di deadlock.


+1 ma: per le tabelle heap è ancora possibile ottenere un deadlock di conversione anche con i blocchi di aggiornamento: sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/11/…
AK

Bizzarro, @alex. Immagino che abbia a che fare con una condizione di gara del motore che cerca di trovare cosa bloccare prima di bloccarlo effettivamente ...
ErikE

@ErikE - Il deadlock di conversione nell'articolo di Alex sta convertendo da IXa Xsullo stesso heap. È interessante notare che nessuna riga si qualifica, quindi non vengono mai eliminati i blocchi di riga. Non sono sicuro del motivo per cui prende il Xblocco a tutti.
Martin Smith,

11

Penso che l'approccio migliore per te sarebbe quello di esporre effettivamente il tuo modulo ad alta concorrenza e vedere di persona. A volte UPDLOCK da solo è sufficiente e non è necessario HOLDLOCK. A volte sp_getapplock funziona molto bene. Non vorrei fare alcuna dichiarazione generale qui - a volte l'aggiunta di un altro indice, trigger o vista indicizzata modifica il risultato. Dobbiamo stressare il codice del test e vedere di persona caso per caso.

Ho scritto diversi esempi di stress test qui

Modifica: per una migliore conoscenza degli interni, puoi leggere i libri di Kalen Delaney. Tuttavia, i libri potrebbero non essere sincronizzati come qualsiasi altra documentazione. Inoltre, ci sono troppe combinazioni da considerare: sei livelli di isolamento, molti tipi di blocchi, indici cluster / non cluster e chissà cos'altro. Sono molte le combinazioni. Inoltre, SQL Server è un codice sorgente chiuso, quindi non possiamo scaricare codice sorgente, eseguirne il debug e così via: sarebbe la fonte di conoscenza definitiva. Qualsiasi altra cosa potrebbe essere incompleta o obsoleta dopo la prossima versione o service pack.

Quindi, non dovresti decidere cosa funziona per il tuo sistema senza i tuoi stress test. Qualunque cosa tu abbia letto, può aiutarti a capire cosa sta succedendo, ma devi dimostrare che i consigli che hai letto funzionano per te. Non penso che nessuno possa farlo per te.


9

In questo caso particolare, l'aggiunta di un UPDLOCKlucchetto a SELECTimpedirebbe effettivamente anomalie. L'aggiunta di HOLDLOCKnon è necessaria poiché viene mantenuto un blocco degli aggiornamenti per la durata della transazione, ma confesso di includerlo anch'io come un'abitudine (possibilmente negativa) in passato.

Immagina di fare qualcosa di più legittimo di un aggiornamento ID, alcuni calcoli basati su una lettura che devi aggiornare. Potrebbero esserci molte tabelle coinvolte, alcune delle quali scriverai e altre no. Qual è la migliore pratica qui?

Non esiste una buona pratica. La scelta del controllo della concorrenza deve essere basata sui requisiti dell'applicazione. Alcune applicazioni / transazioni devono essere eseguite come se avessero la proprietà esclusiva del database, evitando anomalie e imprecisioni a tutti i costi. Altre applicazioni / transazioni possono tollerare un certo grado di interferenza reciproca.

  • Recupero di un livello di stock fasciato (<5, 10+, 50+, 100+) per un prodotto in un negozio web = lettura sporca (imprecisa non importa).
  • Verifica e riduzione del livello delle scorte su quel checkout del negozio web = lettura ripetibile (DEVE avere lo stock prima di vendere, NON DEVE finire con un livello di stock negativo).
  • Spostamento di denaro contante tra il mio conto corrente e quello di risparmio presso la banca = serializzabile (non calcolare in modo errato o collocare in modo errato il mio denaro!).

Modifica: il commento di @ AlexKuznetsov mi ha spinto a rileggere la domanda e a rimuovere l'errore molto evidente nella mia risposta. Nota per sé su post a tarda notte.

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.