2009-02-22 10 views
5

Ho una procedura memorizzata, che seleziona 1 record indietro. la stored procedure può essere chiamata da diverse applicazioni su diversi PC. L'idea è che la stored procedure riporti il ​​successivo record che deve essere elaborato, e se due applicazioni chiamano il proc memorizzato nello stesso momento, lo stesso record non dovrebbe essere riportato indietro. La mia domanda è sotto, sto cercando di scrivere la query nel modo più efficiente possibile (sql 2008). Può essere fatto in modo più efficiente di questo?Transazione efficiente, blocco record

CREATE PROCEDURE GetNextUnprocessedRecord 
AS 
BEGIN 
    SET NOCOUNT ON; 

    --ID of record we want to select back 
    DECLARE @iID BIGINT  

    -- Find the next processable record, and mark it as dispatched 
    -- Must be done in a transaction to ensure no other query can get 
    -- this record between the read and update 
    BEGIN TRAN 

     SELECT TOP 1 
      @iID = [ID] 
     FROM 
      --Don't read locked records, only lock the specific record 
      [MyRecords] WITH (READPAST, ROWLOCK) 
     WHERE 
      [Dispatched] is null 
     ORDER BY 
      [Received] 

     --Mark record as picked up for processing 
     UPDATE 
      [MyRecords] 
     SET 
      [Dispatched] = GETDATE() 
     WHERE 
      [ID] = @iID  

    COMMIT TRAN 

    --Select back the specific record 
    SELECT 
     [ID], 
     [Data] 
    FROM  
     [MyRecords] WITH (NOLOCK, READPAST) 
    WHERE 
     [ID] = @iID 

END 
+0

Io non sono convinto che questo TSQL è transazionale sicuro ... –

+0

prova a mettere un WAITFOR DELAY '0: 2: 0' dopo SELECT e prima dell'aggiornamento, esegui l'SP ed esegui lo stesso SP da un'altra connessione ... –

+0

in realtà, mi sbaglio! HOLDLOCK ha lo stesso effetto di REPEATABLEREAD nella tabella MyRecords. –

risposta

3

L'utilizzo del suggerimento di blocco READPAST è corretto e SQL sembra OK.

mi piacerebbe aggiungere l'uso XLOCK però che è anche HOLDLOCK/SERIALIZABLE

... 
[MyRecords] WITH (READPAST, ROWLOCK, XLOCK) 
... 

Questo significa che si ottiene l'ID, e bloccare in esclusiva che fila mentre si portano avanti e aggiorna.

Modifica: aggiungi un indice sulle colonne Inviato e Ricevuto per renderlo più veloce. Se [ID] (presumo che sia il PK) non è in cluster, INCLUDE [ID]. E filtrare l'indice anche perché è SQL 2008

È anche possibile utilizzare questo costrutto, che fa tutto in una volta, senza XLOCK o HOLDLOCK

UPDATE 
    MyRecords 
SET 
    --record the row ID 
    @id = [ID], 
    --flag doing stuff 
    [Dispatched] = GETDATE() 
WHERE 
    [ID] = (SELECT TOP 1 [ID] FROM MyRecords WITH (ROWLOCK, READPAST) WHERE Dispatched IS NULL ORDER BY Received) 

UPDATE, assign, set in one

+0

Consigli meravigliosi! Ho aggiunto l'hint XLOCK e creato un indice non cluster su Received and Dispatched e incluso ID con filtro "Inviato IS NULL". Questo è in aggiunta al mio indice cluster su ID. Sto ancora cercando di capire se l'Update assign set in one è più veloce o meno. – Jeremy

+1

Grazie. Mi preoccuperei di più dell'integrità della transazione rispetto alle prestazioni definitive. La singola istruzione rimuove la necessità di XLOCK. – gbn

0

È possibile assegnare ad ogni processo picker un ID univoco, e aggiungere colonne pickerproc e pickstate ai record. Poi

UPDATE MyRecords
SET pickerproc = myproc,
pickstate = 'I' - per 'processo I'n
WHERE Id = (SELECT MAX (Id) FROM WHERE MyRecords pickstate = 'A') - 'A disponibile

Questo ti porta il tuo record in una fase atomica e puoi eseguire il resto della tua elaborazione a tuo piacimento. Quindi puoi impostare Pickstate su 'Completo', 'E'rror, o qualsiasi altra cosa quando viene risolto.

Penso che Mitch si riferisca a un'altra buona tecnica in cui si crea una tabella di code di messaggi e si inseriscono gli ID lì. Esistono diversi thread SO: cerca 'message queue table'.

+0

che non è sicuramente sicuro! –

+0

E perché dovrebbe essere? Non distribuirà lo stesso MAX (Id) con lo stato Pickstate 'A' fino a quando non lo avrai cambiato. Almeno non ha nella mia esperienza basata su probabilmente 10^6 + istanze. – dkretz

+0

..e non sarà efficiente né –

0

È possibile mantenere i MyRecords su una tabella "MEMORY" per un'elaborazione più rapida.

+0

La tabella potrebbe diventare piuttosto grande, inoltre, cosa succede se il server si arresta in modo anomalo? – Jeremy