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:
WITH(TABLOCKX, HOLDLOCK)
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.