2010-09-03 14 views
6

Ho bisogno di avere una tabella di database MsSql e altri 8 (identici) processi che accedono alla stessa tabella in parallelo - facendo una selezione in alto n, elaborando quelle n righe e aggiornando un colonna di quelle file. Il problema è che ho bisogno di selezionare ed elaborare ogni riga solo una volta. Ciò significa che se un processo arriva al database e seleziona le prime n righe, quando arriva il secondo processo dovrebbe trovare quelle righe bloccate e selezionare le righe da n a 2 * n righe, e così via ...Restituisce righe sbloccate in una query "select top n"

È possibile mettere un blocco su alcune righe quando le selezioni e quando qualcuno richiede le prime n righe che sono bloccate per restituire le righe successive e non aspettare quelle bloccate? Sembra un tentativo lungo, ma ...

Un'altra cosa a cui stavo pensando - forse non così elegante ma suona semplice e sicura, è quella di avere nel database un contatore per le istanze che ha selezionato su quel tavolo. La prima istanza che viene incrementa il contatore e seleziona top n, la successiva incrementa il contatore e seleziona le righe da n * (i-1) a n * i, e così via ...

Questo suono come una buona ideea? Hai qualche suggerimento migliore? Ogni pensiero è molto apprezzato!

Grazie per il vostro tempo.

+3

Sembra che tu stia utilizzando un tavolo come una coda? Potresti trovare [questo articolo] (http: // rusanu.it/2010/03/26/using-tables-as-queues /) di [Remus Rusanu] (http://stackoverflow.com/users/105929/remus-rusanu) utile. –

+0

Grazie per il link. E 'stato interessante saperlo, ma dato che ho bisogno di tenere le righe dopo averle selezionate (eliminarle non è un'opzione), non è davvero applicabile al mio caso. – Diana

+1

Si potrebbe avere una colonna di bit 'processed' e sostituire un' UPDATE' per un 'DELETE' possibilmente. Se si decide di utilizzare una soluzione di conteggio [questa risposta] (http://stackoverflow.com/questions/3453411/sql-server-auto-incrementation-that-allows-update-statements/3462957#3462957) potrebbe essere d'aiuto. –

risposta

6

Ecco un esempio I blogged about a while ago:

Il suggerimento READPAST è ciò che assicura più processi non bloccano a vicenda quando il polling per i record da elaborare. Inoltre, in questo esempio ho un campo di bit per "bloccare" fisicamente un record - potrebbe essere un datetime se necessario.

DECLARE @NextId INTEGER 
BEGIN TRANSACTION 

-- Find next available item available 
SELECT TOP 1 @NextId = ID 
FROM QueueTable WITH (UPDLOCK, READPAST) 
WHERE IsBeingProcessed = 0 
ORDER BY ID ASC 

-- If found, flag it to prevent being picked up again 
IF (@NextId IS NOT NULL) 
    BEGIN 
     UPDATE QueueTable 
     SET IsBeingProcessed = 1 
     WHERE ID = @NextId 
    END 

COMMIT TRANSACTION 

-- Now return the queue item, if we have one 
IF (@NextId IS NOT NULL) 
    SELECT * FROM QueueTable WHERE ID = @NextId 
+0

Grazie per il vostro anser, READPAST suona come la risposta MsSql che stavo cercando. Se SELECT TOP 1 @NextId = ID è una top 3000 selezionata su più tabelle di milioni di record, potrebbe richiedere del tempo. È "garantito" dal READPAST che se ho un altro thread che fa la stessa identica selezione, il secondo raccoglierà le prossime righe non bloccate? – Diana

+0

Ho testato il tuo approccio su una semplice tabella di test, funziona come un incantesimo. Ma sul mio tavolo reale (preatty big, molti indici e statistiche su di esso), funziona solo a volte ... Altre volte, non salta le righe bloccate, semplicemente "si blocca" fino a quando la selezione precedente che blocca le righe è finita (Per testare questo ho aggiunto aw "aitfor delay '00: 00: 10 '"). Qualche idea per cui questo sta accadendo? – Diana

+0

@Diana - Quante righe stai cercando di bloccare per processo? per esempio. forse un blocco di tabella è in corso (gli scalmi verranno aggiornati ai blocchi di tabelle oltre una certa soglia). Se potessi pubblicare uno script di esempio che dimostri come lo stai facendo (come il mio esempio era solo per un singolo record), sarebbe fantastico - potrebbe valere in una domanda collegata per la massima esposizione – AdaTheDev

2

Il metodo più semplice è quello di utilizzare row locking:

BEGIN TRAN 

SELECT * 
FROM authors 
WITH (HOLDLOCK, ROWLOCK) 
WHERE au_id = '274-80-9391' 

/* Do all your stuff here while the record is locked */ 

COMMIT TRAN 

Ma se siete accedere ai dati e quindi chiudere la connessione, non sarà in grado di utilizzare questo metodo.

Per quanto tempo sarà necessario bloccare le righe? Il modo migliore potrebbe essere come dici tu per posizionare un contatore sulle righe selezionate (operazione migliore utilizzando la clausola OUTPUT all'interno di UPDATE).

+0

Ho bisogno di chiudere la connessione dopo la selezione e l'elaborazione del batch di righe richiederà un po 'di tempo (ogni riga determinerà l'invio di una e-mail con un allegato da 500kb). – Diana

+0

Direi che è necessario implementare il proprio metodo per bloccare le righe (come il contatore) poiché il blocco delle righe può bloccare le richieste abbastanza a lungo da far sì che gli errori di timeout vengano visualizzati altrove. Il commento di Martin sembra un modo per andare avanti http://stackoverflow.com/questions/3636950/return-unlocked-rows-in-a-select-top-n-query#comment-3822218 – Codesleuth

0

L'idea migliore se si desidera selezionare i record in questo modo sarebbe utilizzare un contatore in una tabella separata.

In realtà non si desidera bloccare le righe su un database di produzione esclusivamente per un lungo periodo di tempo, pertanto è consigliabile utilizzare un contatore. In questo modo solo uno dei tuoi processi sarebbe in grado di afferrare quel numero di contatore alla volta (dato che verrà bloccato mentre viene aggiornato) che ti darà la concorrenza di cui hai bisogno.

Se hai bisogno di una mano a scrivere le tabelle e le procedure che lo faranno (in modo semplice e sicuro come lo metti!) Basta chiedere.

+0

Ok, felice di vedere che l'idea di il contatore suona abbastanza bene e nessun flusso maggiore può essere visto a prima vista. Ci penserò più per un giorno o due. Grazie per la tua risposta e aiuto! – Diana

0

MODIFICA: ahh, non importa, stai lavorando in uno stile disconnesso. Che ne dite di questo:

UPDATE TOP (@n) QueueTable SET Locked = 1 
OUTPUT INSERTED.Col1, INSERTED.Col2 INTO @this 
WHERE Locked = 0 

<do your stuff> 

Forse si stanno cercando per il READPAST suggerimento?

<begin or save transaction> 

INSERT INTO @this (Col1, Col2) 
SELECT TOP (@n) Col1, Col2 
FROM Table1 WITH (ROWLOCK, HOLDLOCK, READPAST) 

<do your stuff> 

<commit or rollback> 
Problemi correlati