Transazione efficiente, blocco record


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?

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 ... 22 feb. 092009-02-22 08:07:45

  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 ... 22 feb. 092009-02-22 08:17:24

  0

in realtà, mi sbaglio! HOLDLOCK ha lo stesso effetto di REPEATABLEREAD nella tabella MyRecords. 22 feb. 092009-02-22 08:22:27

  0

È meglio essere sicuro per impostazione predefinita. Altrimenti c'è un caos concettuale diffuso in libertà. 22 feb. 092009-02-22 08:43:46

  0

Continuo a pensare che sarebbe necessario rendere la transazione SERIALIZZABILE piuttosto che REPEATABLEREAD 22 feb. 092009-02-22 08:55:17

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. 23 feb. 092009-02-23 07:07:14

+1

Grazie. Mi preoccuperei di più dell'integrità della transazione rispetto alle prestazioni definitive. La singola istruzione rimuove la necessità di XLOCK. 23 feb. 092009-02-23 07:48:33


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! 22 feb. 092009-02-22 08:51:33

  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. 22 feb. 092009-02-22 08:55:38

  0

..e non sarà efficiente né 22 feb. 092009-02-22 08:57:49

  0

Sto ascoltando ... qual è il tuo argomento? Posso sempre imparare di più su SQL ... 22 feb. 092009-02-22 09:00:29

  0

@le dorfier: per renderlo coerente a livello di transazione, è necessario che sia racchiuso in una transazione SERIALIZZABILE. In caso contrario, vi è il rischio di letture sporche/letture non ripetibili/letture phantom/ 22 feb. 092009-02-22 09:01:27

  0

a meno che non mi sbaglio, l'operazione MAX sta per eseguire la scansione di righe. Se questo è un grande tavolo, manterrebbe aperto un periodo di transizione per un tempo relativamente lungo. 22 feb. 092009-02-22 09:03:12

  0

Non mi piacerebbe dover capire di nuovo quel documento di blocco turgid, ma parto dal presupposto che ACID si applica di default. Non sei d'accordo? 22 feb. 092009-02-22 09:03:40

  0

(Dopo la riflessione) Vedo il tuo punto (cioè la bassa cardinalità della colonna di pickstate). Ma è risolto dall'indice "filtrato" disponibile in SQL 2008. Vedi http://blog.sqlauthority.com/2008/09/01/sql-server-2008-introduction-to-filtered-index-improve-performance-with -filtered-index/ 22 feb. 092009-02-22 09:18:22


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? 22 feb. 092009-02-22 19:27:06