2009-11-30 5 views
6

Sto scrivendo un servizio in background che deve elaborare una serie di lavori, memorizzati come record in una tabella sqlserver. Il servizio deve trovare i 20 lavori più vecchi che devono essere lavorati (where status = 'new'), contrassegnarli (set status = 'processing'), eseguirli e aggiornare i lavori in seguito.Contrassegna e restituisce un gruppo di righe nella banca dati in modo atomico

È la prima parte con cui ho bisogno di aiuto. Potrebbero esserci più thread che accedono al database contemporaneamente e voglio assicurarmi che la query "mark & return" venga eseguita in modo atomico, o quasi.

Questo servizio impiegherà relativamente poco tempo per accedere al database e non è la fine del mondo se un lavoro viene eseguito due volte, quindi potrei essere in grado di accettare una piccola probabilità di lavori in esecuzione più di una volta per una maggiore semplicità nel codice.

Qual è il modo migliore per farlo? Sto usando linq-to-sql per il mio livello dati, ma presumo che dovrò cadere in t-sql per questo.

risposta

10

La tabella di posti di lavoro è una coda. Scrivere tabelle di backup delle tabelle degli utenti è notoriamente soggetto a errori in quanto porta a problemi di deadlock e di concurency.

La cosa più semplice sarebbe quella di eliminare la tabella utente e utilizzare invece un vero queue. Questo ti darà la possibilità di liberare la coda senza concessioni sulla base di codice testata e convalidata del sistema. Il problema è che l'intero paradigma attorno alle code cambia da INSERT e DELETE/UPDATE a SEND/RECEIVE. D'altra parte con la coda incorporata si ottengono alcuni gadget gratuiti molto potenti, ovvero Activation e correlated items locking.

Se si desidera continuare lungo il sentiero della tabella user code sostenuta poi la secondo più importante trucco per iscritto code tabelle utente è quello di utilizzare UPDATE ... USCITA:

WITH cte AS (
    SELECT TOP(20) status, id, ... 
    FROM table WITH (ROWLOCK, READPAST, UPDLOCK) 
    WHERE status = 'new' 
    ORDER BY enqueue_time) 
UPDATE cte 
    SET status = 'processing' 
OUTPUT 
    INSERTED.id, ... 

La sintassi è CTE solo per comodità di posizionare correttamente il TOP e l'ORDER BY, la query può essere scritta utilizzando le tabelle derivate esattamente come. Non è possibile utilizzare direttamente UPDATE ...TOP perché UPDATE non supporta un ordine BY e si richiede che questo soddisfi la parte 'più vecchia' del requisito. I suggerimenti di blocco sono necessari per facilitare un'elevata concurenza tra thread di elaborazione paralleli.

Ho detto che questo è il secondo trucco più importante. Il più importante è come organizzi il tavolo. Per una coda, lo deve essere raggruppato in (status, enqueue_time). Se non organizzi correttamente il tavolo, finirai con i deadlock. Commento preventivo: la frammentazione è irrilevante in questo scenario.

+0

Puoi spiegare perché ci saranno deadlock se la tabella non è raggruppata da (status, enqueue_time) anche dopo aver usato i 3 suggerimenti che prescrivi? –

+0

Non conoscevo la clausola OUTPUT, che insieme ai suggerimenti fornisce una soluzione completa. Questo risponde alla mia domanda in SO. –

8

Si prega di vedere la mia risposta qui: SQL Server Process Queue Race Condition che gestisce anche 20 righe in una volta.

Fondamentalmente, in SQL Server è abbastanza semplice gestire la concorrenza e il polling utilizzando i suggerimenti ROWLOCK, READPAST e UPDLOCK.

non posso commentare su LINQ, ma una transazione lascia ancora si apre a problemi di concorrenza: è necessario utilizzare i suggerimenti che ho citato

+0

I tuoi altri articoli sono stati molto utili. Mi mancava uno dei tre suggerimenti. –

1

So che è fuori tema, ma per questo si potrebbe usare MSMQ. Una coda di messaggi metterebbe i tuoi lavori in sequenza ed è thread-safe. È anche possibile assegnare un numero di priorità MSMQ di gestione autonomo. Puoi usare read o peek per cancellare un messaggio dalla coda o semplicemente vedere cosa c'è. Puoi usare il modello di progettazione del comando per aiutarti con questo.

+0

La coda è la risposta, ma perché MSMQ quando SQL Server viene fornito con code incorporate? –

+0

Il modo in cui li utilizzo è quello di controllare i processi. Quando accodo qualcosa, non uso affatto il database. Quindi qualsiasi listner può trovare un lavoro da fare. E l'ho testato con 5 computer con 10 processi ciascuno e non ho mai avuto un problema di concorrenza. Immagino che dipenda da dove vuoi che tu rimanga in coda. –

0

Non è semplice come eseguire il T-SQL all'interno di una transazione o mi manca qualcosa?

4

Sulla gbn's answer ...

Se stai usando SQL Server 2005 o più recente, si può restituire le righe aggiornate atomicamente utilizzando un OUTPUT clause nella tua UPDATE dichiarazione:

UPDATE TOP (20) your_table 
SET status = 'processing' 
OUTPUT INSERTED.* 
FROM your_table WITH (ROWLOCK, READPAST, UPDLOCK) 
WHERE status = 'new'