Transacción eficiente, bloqueo de registros


5

Tengo un procedimiento almacenado, que selecciona 1 registro de nuevo. el procedimiento almacenado podría llamarse desde varias aplicaciones diferentes en diferentes PC. La idea es que el procedimiento almacenado recupere el siguiente registro que debe procesarse, y si dos aplicaciones llaman al proceso almacenado al mismo tiempo, no se debe recuperar el mismo registro. Mi consulta está debajo, intento escribir la consulta de la manera más eficiente posible (sql 2008). ¿Se puede hacer de manera más eficiente que esto?

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

No estoy convencido de que esto TSQL es transaccionalmente seguro ... 22 feb. 092009-02-22 08:07:45

  0

intente poner WAITFOR DELAY '0: 2: 0' después del SELECT y antes de la ACTUALIZACIÓN, ejecute el SP y ejecute el mismo SP desde otra conexión ... 22 feb. 092009-02-22 08:17:24

  0

en realidad, ¡estoy equivocado! El HOLDLOCK tiene el mismo efecto que REPEATABLEREAD en la tabla MyRecords. 22 feb. 092009-02-22 08:22:27

  0

Es mejor estar seguro por defecto. De lo contrario, hay un caos conceptual generalizado suelto. 22 feb. 092009-02-22 08:43:46

  0

¡Todavía pienso que necesitaría hacer la transacción SERIALIZABLE en lugar de REPEATABLEREAD 22 feb. 092009-02-22 08:55:17

3

El uso de la sugerencia de bloqueo de READAPTO es correcto y su SQL se ve bien.

Yo añadiría uso XLOCK pesar de que también es HOLDLOCK/SERIALIZABLE

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

Esto significa que usted obtiene el ID y el bloqueo exclusivamente esa fila, mientras que continuar y actualizarlo.

Editar: agregue un índice en las columnas enviadas y recibidas para hacerlo más rápido. Si [ID] (supongo que es el PK) no está agrupado, INCLUDE [ID]. Y filtrar el índice también porque es SQL 2008

También es posible usar esta construcción, que lo hace todo de una vez sin 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

¡Extremidades maravillosas! He agregado la sugerencia de XLOCK y he creado un índice no agrupado en Recibido y despachado, y he incluido el ID con un filtro de "Despachado IS NULL". Esto es adicional a mi índice agrupado en ID. Todavía estoy tratando de averiguar si el conjunto de asignación de actualización en uno es más rápido o no. 23 feb. 092009-02-23 07:07:14

+1

Gracias. Me preocuparía más la integridad transaccional que el rendimiento total. La declaración única elimina la necesidad de XLOCK. 23 feb. 092009-02-23 07:48:33


0

Puede asignar a cada proceso de selector una ID única y agregar columnas pickerproc y pickstate a sus registros. Entonces

MyRecords ACTUALIZACIÓN
SET pickerproc = MyProc,
pickstate = 'I' - para 'proceso I'n
donde id = (SELECT MAX (Id) de MyRecords DONDE pickstate = 'A') - 'A' disponible

Eso le permite grabar en un paso atómico, y puede hacer el resto de su procesamiento en su tiempo libre. Luego puede configurar pickstate en 'C'omplete', 'E'rror o lo que sea cuando se resuelva.

Creo que Mitch se está refiriendo a otra buena técnica en la que creas una tabla de cola de mensajes e insertas los Id. Allí. Hay varios hilos SO: busque 'tabla de cola de mensajes'.

  0

que definitivamente no es segura! 22 feb. 092009-02-22 08:51:33

  0

¿Y por qué sería eso? No va a entregar el mismo MAX (Id) con pickstate 'A' hasta después de haberlo cambiado. Por lo menos, en mi experiencia no se ha basado en probablemente 10^6 + instancias. 22 feb. 092009-02-22 08:55:38

  0

..y tampoco será eficiente 22 feb. 092009-02-22 08:57:49

  0

Estoy escuchando ... ¿cuál es su argumento? Siempre puedo aprender más sobre SQL ... 22 feb. 092009-02-22 09:00:29

  0

@le dorfier: para hacer que la transacción sea coherente, debería estar envuelto en una transacción SERIALIZABLE. De lo contrario, existe el riesgo de lecturas sucias/lecturas no repetibles/lecturas fantasma/ 22 feb. 092009-02-22 09:01:27

  0

, a menos que esté equivocado, la operación MAX va a escanear filas. Si esta es una gran mesa, estaría sosteniendo una sesión de apertura durante un tiempo relativamente largo. 22 feb. 092009-02-22 09:03:12

  0

No me gustaría tener que descubrir ese documento de bloqueo turgente de nuevo, pero empiezo por suponer que ACID se aplica por defecto. ¿No estás de acuerdo? 22 feb. 092009-02-22 09:03:40

  0

(Después de reflexionar) Veo su punto (es decir, la baja cardinalidad de la columna de pickstate). Pero se resuelve con el "índice filtrado" disponible en SQL 2008. Consulte 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

Puede mantener MyRecords en una mesa "MEMORY" para un procesamiento más rápido.

  0

La tabla podría ser bastante grande, además, ¿qué sucede si el servidor falla? 22 feb. 092009-02-22 19:27:06