sql >> Base de Datos >  >> RDS >> Sqlserver

Cómo obtener el siguiente número en una secuencia

Si no mantiene una mesa de mostrador, hay dos opciones. Dentro de una transacción, primero seleccione el MAX(seq_id) con una de las siguientes sugerencias de tabla:

  1. WITH(TABLOCKX, HOLDLOCK)
  2. WITH(ROWLOCK, XLOCK, HOLDLOCK)

TABLOCKX + HOLDLOCK es un poco exagerado. Bloquea declaraciones de selección regulares, que pueden considerarse pesadas aunque la transacción sea pequeña.

A ROWLOCK, XLOCK, HOLDLOCK la sugerencia de mesa es probablemente una mejor idea (pero:lea la alternativa con una mesa de mostrador más adelante). La ventaja es que no bloquea las declaraciones de selección regulares, es decir, cuando las declaraciones de selección no aparecen en un SERIALIZABLE transacción, o cuando las declaraciones de selección no proporcionan las mismas sugerencias de tabla. Usando ROWLOCK, XLOCK, HOLDLOCK seguirá bloqueando declaraciones de inserción.

Por supuesto, debe asegurarse de que ninguna otra parte de su programa seleccione el MAX(seq_id) sin estas sugerencias de tabla (o fuera de un SERIALIZABLE transacción) y luego use este valor para insertar filas.

Tenga en cuenta que, dependiendo del número de filas bloqueadas de esta manera, es posible que SQL Server aumente el bloqueo a un bloqueo de tabla. Obtenga más información sobre la elevación de bloqueos aquí .

El procedimiento de inserción usando WITH(ROWLOCK, XLOCK, HOLDLOCK) quedaría de la siguiente manera:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @max_seq INT=(SELECT MAX(seq) FROM dbo.table_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @max_seq IS NULL SET @max_seq=0;
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@max_seq+1,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

Una alternativa y probablemente una mejor idea es tener un contador mesa, y proporcione estas sugerencias de mesa en la mesa del mostrador. Esta tabla se vería así:

CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);

Entonces cambiaría el procedimiento de inserción de la siguiente manera:

DECLARE @target_model INT=3;
DECLARE @part VARCHAR(128)='Spine';
BEGIN TRY
    BEGIN TRANSACTION;
    DECLARE @new_seq INT=(SELECT seq FROM dbo.counter_seq WITH(ROWLOCK,XLOCK,HOLDLOCK) WHERE [email protected]_model);
    IF @new_seq IS NULL 
        BEGIN SET @new_seq=1; INSERT INTO dbo.counter_seq(model,seq)VALUES(@target_model,@new_seq); END
    ELSE
        BEGIN SET @new_seq+=1; UPDATE dbo.counter_seq SET [email protected]_seq WHERE [email protected]_model; END
    INSERT INTO dbo.table_seq(part,seq,model)VALUES(@part,@new_seq,@target_model);
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
END CATCH

La ventaja es que se utilizan menos bloqueos de fila (es decir, uno por modelo en dbo.counter_seq ), y la escalada de bloqueo no puede bloquear todo dbo.table_seq table bloqueando así las sentencias select.

Puedes probar todo esto y ver los efectos tú mismo, colocando un WAITFOR DELAY '00:01:00' después de seleccionar la secuencia de counter_seq y jugar con la(s) tabla(s) en una segunda pestaña de SSMS.

PS1:Uso de ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID) no es una buena manera. Si se eliminan/agregan filas, o si se modifican los ID, la secuencia cambiará (considere los ID de las facturas que nunca deberían cambiar). Además, en términos de rendimiento, tener que determinar los números de fila de todas las filas anteriores al recuperar una sola fila es una mala idea.

PS2:nunca usaría recursos externos para proporcionar bloqueo, cuando SQL Server ya proporciona bloqueo a través de niveles de aislamiento o sugerencias de tablas detalladas.