2015-03-25 11 views
6

Sto implementando una coda in SQL avendo una tabella che ha una colonna 'supportedby'. Ognuno di un certo numero di processi può afferrare un oggetto da questa coda popolando il campo claimby.È un modo "thread-safe" per aggiornare e restituire una riga in SQL da una coda?

Quello che voglio fare è impedire che due processi afferrino lo stesso oggetto.

Ho fatto qualche ricerca e ho trovato alcune possibilità su come farlo. Non voglio usare il broker, voglio rimanere con una tabella semplice.

Sto usando C# per lavorare sull'elemento che viene recuperato.

Ho giocato in giro in SQL e scritto il seguente:

declare @test table (ID int); 

UPDATE TOP (1) CatQueueItem SET ClaimedBy = 'Mittens' 
OUTPUT inserted.CatQueueItemIdId into @test 
WHERE ClaimedBy is null 

Ciò avverrà come una singola query in C# aggiungendo il parametro come parametro di output, ecc

I Mi chiedo se questo codice funzionerà o se dovrò pensare al blocco e alle transizioni per garantire che se più processi eseguono simultaneamente questa query che funzionerà come previsto (solo un processo rivendicherà l'articolo, gli altri aggiornamenti salteranno questa riga interamente dovuto al fatto che è stato aggiornato dal primo).

Qualche idea?

+0

Interessante - Non ho mai visto "OUTPUT" prima. La [pagina dei documenti] (https://msdn.microsoft.com/en-us/library/ms177564.aspx) ha un'intestazione specifica per "Code" che sembra implicare che questo dovrebbe andare bene –

risposta

7

Sebbene la query fornita non fornisca una riga a più thread a causa di una transazione implicita creata, può causare problemi in cui un thread gestisce la coda. Se si desidera interrompere questa operazione, è possibile aggiungere gli interrogativi READ PAST e ROW LOCK alla query. Ciò impedisce il blocco di un thread dal bloccare gli altri thread dall'ottenere una riga.

Ad esempio:

UPDATE TOP (1) CatQueueItem WITH (READPAST, ROWLOCK) 
SET ClaimedBy = 'Mittens' 
OUTPUT inserted.CatQueueItemIdId into @test 
WHERE ClaimedBy is null 

La spiegazione lungo

Tutte le dichiarazioni di SQL Server eseguiti nel contesto di una transazione. Se viene eseguita un'istruzione e non viene specificata alcuna transazione, SQL Server crea una transazione implicita solo per quella singola istruzione.

Esistono tre tipi principali di blocchi che SQL Server utilizza Shared (S), Update (U) ed Exclusive (X). I blocchi vengono acquisiti nell'ambito di una transazione. Se una transazione ha un blocco S su una riga, altre transazioni possono ottenere un blocco S o U ma non un blocco X sulla stessa riga. Se una transazione ha un blocco U su una riga, altre transazioni possono ottenere solo blocchi S su quella riga. Se una transazione ha un blocco X su una riga, nessuna altra transazione può ottenere un blocco sulla riga. Per scrivere su una riga, una transazione deve avere un blocco X poiché ciò ha l'effetto di bloccare tutte le altre transazioni dalla lettura della riga mentre è in parte l'aggiornamento.

Questo dà la seguente tabella di compatibilità:

S U X 
    ---------- 
S | Y Y N 
U | Y N N 
X | N N N 

L'aggiornamento che è in questione ha due parti ad esso. Prima fa una lettura della tabella per trovare una riga che ha un valore null in ClaimedBy. Una volta trovata la riga, la seconda parte dell'operazione aggiorna la riga trovata.

Normalmente durante la lettura da tabelle SQL Server utilizza i blocchi S poiché questi non impediscono ad altre transazioni di leggere anche le righe e aumenta le prestazioni di lettura, ma impediscono ad altre transazioni di ottenere i blocchi X per scrivere nelle righe. Il problema è che quando la seconda parte della query di aggiornamento tenta di eseguire l'aggiornamento a un blocco X, in modo che possa scrivere sulla riga, può causare un deadlock. Il motivo è che la prima parte della query in un'altra transazione potrebbe aver acquisito un blocco S identico alla transazione, ma potrebbe non averlo ancora aggiornato. Ciò impedisce alla transazione di aggiornare il blocco a un blocco X e il blocco S impedisce l'aggiornamento dell'altra transazione. Nessuna delle transazioni sarà in grado di avere successo in modo da bloccarle. In questo caso, SQL Server preleva una transazione e la riporta indietro.

Per interrompere il deadlock, quando si esegue la parte letta di una dichiarazione di aggiornamento SQL Server utilizza i blocchi U. I blocchi U consentono ad altre transazioni di acquisire i blocchi S consentendo a coloro che stanno facendo le letture di avere successo, ma non consente altri blocchi U. Usando un blocco U stai dicendo che stai solo leggendo, ma hai intenzione di scrivere ad un certo punto nel futuro. Ciò impedisce la situazione in cui si hanno due transazioni che tentano entrambi di eseguire l'aggiornamento a un blocco X. In quanto tale, la transazione che ha il blocco U può eseguire l'upgrade a X safe sapendo che non si verificherà un deadlock con un'altra transazione.

La pertinenza di tutto questo nello scenario indicato nella domanda è che i blocchi U utilizzati dalla transazione di un thread per bloccare le righe durante la ricerca di un blocco di righe disponibile su tutte le transazioni del thread. Questo perché quando una transazione tenta di acquisire un blocco su una riga che ha già un blocco incompatibile su di esso, semplicemente attende in una coda fino a quando il blocco del blocco non viene sbloccato. Poiché tutti i thread stanno cercando le stesse file per uno libero, tutti cercano di ottenere i blocchi U sulla stessa riga e tutti si formano in una coda ordinata in attesa di ottenere un blocco U sulla stessa riga. In altre parole, la transazione di un solo thread può cercare le righe libere alla volta.

Ciò che l'hint della tabella READPAST fa è che interrompe l'accodamento delle transazioni per leggere le righe nella tabella. Con READPAST quando una transazione si sforza di acquisire un blocco su una riga che è già bloccata invece di unirsi a una coda per il blocco, dice cose e va e prova la riga successiva. In questo caso dirà che non so se la riga ha un valore ClaimedBy o no, non sono disposto ad aspettare di scoprirlo, quindi supporrò che lo faccia e provi la riga successiva. Ciò potrebbe significare che salta le righe disponibili ma non otterrà una riga non disponibile. Ciò migliorerà la velocità con cui i thread e le loro transazioni possono ottenere elementi dalla coda in quanto possono cercare tutte le righe disponibili contemporaneamente.

L'acquisizione di blocchi può essere potenzialmente piuttosto costosa. Ci vuole tempo e memoria. Per contrastare questo problema, SQL Server presenta diverse granularità per i blocchi. È possibile bloccare l'intero database, un'intera tabella, una pagina di una tabella o una riga di una tabella. Query Optimizer proverà a utilizzare le statistiche per prevedere quante righe devono essere bloccate. Se ce ne sono molte, scegli una pagina o un blocco tabella invece di un blocco riga. Ciò ha l'effetto di richiedere un minor numero di blocchi nel complesso.

L'hint della tabella ROWLOCK indica a SQL Server di non utilizzare questi blocchi granulari più grezzi e utilizzare solo blocchi di riga. Questo è un vantaggio in questo caso perché blocca grandi blocchi di righe disponibili che vengono ignorati dalle transazioni che cercano le righe disponibili.

+0

Se il blocco è impedito isn C'è un problema con due thread che fanno il 'where claimby is null' ed entrambi valutano true, quindi entrambi cercano di afferrarlo? – NibblyPig

+0

Ho aggiunto una spiegazione alla domanda. Ma in breve né READPAST né ROWLOCK impediscono i blocchi o consentono alle transazioni di ignorare i blocchi. –

+1

Grazie amico è una risposta davvero completa e ha perfettamente senso. Molto apprezzato. – NibblyPig

3

Ogni query in SQL Server opera all'interno di una transazione implicita. Poiché stai usando una singola istruzione qui (non più query) il motore gestirà il blocco e il blocco per te.

Finché si gestisce il caso in cui nessun record viene aggiornato nel codice C# questo dovrebbe gestire la concorrenza bene.

Problemi correlati