Se você não mantiver uma mesa de balcão, há duas opções. Dentro de uma transação, primeiro selecione o
MAX(seq_id)
com uma das seguintes dicas de tabela:WITH(TABLOCKX, HOLDLOCK)
WITH(ROWLOCK, XLOCK, HOLDLOCK)
TABLOCKX + HOLDLOCK
é um pouco exagerado. Ele bloqueia comandos select regulares, que podem ser considerados pesados mesmo que a transação seja pequena. A
ROWLOCK, XLOCK, HOLDLOCK
dica de mesa é provavelmente uma ideia melhor (mas:leia a alternativa com uma mesa de contador mais adiante). A vantagem é que ele não bloqueia comandos select regulares, ou seja, quando os comandos select não aparecem em um SERIALIZABLE
transação, ou quando as instruções select não fornecem as mesmas dicas de tabela. Usando ROWLOCK, XLOCK, HOLDLOCK
ainda bloqueará as instruções de inserção. Claro que você precisa ter certeza de que nenhuma outra parte do seu programa selecione o
MAX(seq_id)
sem essas dicas de tabela (ou fora de um SERIALIZABLE
transação) e, em seguida, use esse valor para inserir linhas. Observe que, dependendo do número de linhas bloqueadas dessa maneira, é possível que o SQL Server escale o bloqueio para um bloqueio de tabela. Leia mais sobre o escalonamento de bloqueio aqui .
O procedimento de inserção usando
WITH(ROWLOCK, XLOCK, HOLDLOCK)
ficaria da seguinte forma: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
Uma alternativa e provavelmente uma ideia melhor é ter um contador mesa e forneça essas dicas de mesa na mesa do balcão. Esta tabela teria a seguinte aparência:
CREATE TABLE dbo.counter_seq(model INT PRIMARY KEY, seq_id INT);
Em seguida, você alteraria o procedimento de inserção da seguinte maneira:
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
A vantagem é que menos bloqueios de linha são usados (ou seja, um por modelo em
dbo.counter_seq
), e o escalonamento de bloqueio não pode bloquear todo o dbo.table_seq
table bloqueando assim as instruções select. Você pode testar tudo isso e ver os efeitos você mesmo, colocando um
WAITFOR DELAY '00:01:00'
depois de selecionar a sequência de counter_seq
, e mexendo na(s) tabela(s) em uma segunda guia do SSMS. PS1:usando
ROW_NUMBER() OVER (PARTITION BY model ORDER BY ID)
não é um bom caminho. Se as linhas forem excluídas/adicionadas ou os IDs forem alterados, a sequência mudará (considere os IDs da fatura que nunca devem ser alterados). Também em termos de desempenho, ter que determinar os números de linha de todas as linhas anteriores ao recuperar uma única linha é uma má ideia. PS2:Eu nunca usaria recursos externos para fornecer bloqueio, quando o SQL Server já fornece bloqueio por meio de níveis de isolamento ou dicas de tabela refinadas.