it-swarm.it

Gestione della concorrenza quando si utilizza il modello SELECT-UPDATE

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 WITH (UPDLOCK, HOLDLOCK) a SELECT farà il trucco. Il livello di isolamento della transazione SERIALIZZABILE sembra funzionare anche dal momento che nega a chiunque altro di leggere ciò che hai fatto fino alla fine del tran ( [~ # ~] update [ ~ # ~] : 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 che ti servono, ma apprezzerei l'input di chiunque.

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

25
ErikE

Risolve solo l'aspetto del livello di isolamento SERIALIZABLE. 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 prenderanno un oggetto S lock o index RangeS-S i blocchi dipendono dalla struttura della tabella e da questi blocchi sono compatibili . Ma si bloccheranno l'un l'altro quando tenteranno di acquisire i blocchi necessari per l'aggiornamento (oggetto IX lock o index RangeS-U rispettivamente) che porterà allo stallo.

L'uso di un suggerimento esplicito UPDLOCK invece serializzerà le letture evitando il rischio di deadlock.

11
Martin Smith

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.

11
A-K

In questo caso particolare l'aggiunta di un blocco UPDLOCK a SELECT impedirebbe effettivamente le anomalie. L'aggiunta di HOLDLOCK non è 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 basarsi 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 (impreciso 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.

9