Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Como obter o próximo número em uma sequência


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