Lacunas inesperadas na coluna IDENTITY

17

Estou tentando gerar números únicos de pedidos que começam em 1 e aumentam em 1. Tenho uma tabela PONumber criada usando este script:

CREATE TABLE [dbo].[PONumbers]
(
  [PONumberPK] [int] IDENTITY(1,1) NOT NULL,
  [NewPONo] [bit] NOT NULL,
  [DateInserted] [datetime] NOT NULL DEFAULT GETDATE(),
  CONSTRAINT [PONumbersPK] PRIMARY KEY CLUSTERED ([PONumberPK] ASC)    
);

E um procedimento armazenado criado usando este script:

CREATE PROCEDURE [dbo].[GetPONumber] 
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO [dbo].[PONumbers]([NewPONo]) VALUES(1);
    SELECT SCOPE_IDENTITY() AS PONumber;
END

No momento da criação, isso funciona bem. Quando o procedimento armazenado é executado, ele inicia no número desejado e aumenta em 1.

O estranho é que, se eu desligar ou hibernar o computador, na próxima vez em que o procedimento for executado, a sequência avançará quase 1000.

Veja os resultados abaixo:

Números PO

Você pode ver que o número saltou de 8 para 1002!

  • Por que isso está acontecendo?
  • Como garantir que os números não sejam ignorados assim?
  • Tudo o que preciso é que o SQL gere números que sejam:
    • a) Único garantido.
    • b) incremento na quantidade desejada.

Admito que não sou especialista em SQL. Entendo mal o que SCOPE_IDENTITY () faz? Devo estar usando uma abordagem diferente? Examinei as seqüências no SQL 2012+, mas a Microsoft diz que elas não são garantidas como únicas por padrão.

Ege Ersoz
fonte

Respostas:

24

Esse é um problema conhecido e esperado - a maneira como as colunas IDENTITY são gerenciadas pelo SQL Server foram alteradas no SQL Server 2012 ( alguns antecedentes ); por padrão, ele armazenará em cache 1000 valores e, se você reiniciar o SQL Server, reiniciar o servidor, executar failover, etc., terá que jogar fora esses 1000 valores, porque não haverá uma maneira confiável de saber quantos deles foram realmente emitido. Isso está documentado aqui . Há um sinalizador de rastreamento que altera esse comportamento, de modo que todas as atribuições de IDENTITY sejam registradas *, impedindo essas lacunas específicas (mas não as lacunas de reversões ou exclusões); no entanto, é importante observar que isso pode ser bastante caro em termos de desempenho, portanto, nem vou mencionar o sinalizador de rastreamento específico aqui.

* (Pessoalmente, acho que esse é um problema técnico que pode ser resolvido de maneira diferente, mas como não escrevo o mecanismo, não posso mudar isso.)

Para ser claro sobre como funcionam a IDENTIDADE e a SEQUÊNCIA:

  • Nenhum dos dois é garantido como exclusivo (você deve aplicar isso no nível da tabela, usando uma chave primária ou restrição exclusiva)
  • Nem é garantido que não há falhas (qualquer reversão ou exclusão, por exemplo, produzirá uma lacuna, não obstante esse problema específico)

A exclusividade é fácil de aplicar. Evitar lacunas não é. Você precisa determinar a importância de evitar essas lacunas (em teoria, você não deve se importar com elas, pois os valores de IDENTITY / SEQUENCE devem ser chaves substitutas sem sentido). Se for muito importante, você não deve usar nenhuma das implementações, mas role seu próprio gerador de sequência serializável (veja algumas idéias aqui , aqui e aqui ) - observe que isso matará a simultaneidade.

Muitos antecedentes sobre esse "problema":

Aaron Bertrand
fonte
Essa resposta (exceto a parte "sinalizador de rastreamento") também se aplica à maioria dos outros bancos de dados SQL (os que possuem sequências de qualquer maneira).
mustaccio
Obrigado pela resposta. A exclusividade é o requisito mais importante. As lacunas não são um grande problema, desde que não sejam grandes. por exemplo, passar de 1 para 4 seria aceitável, mas de 4 para 1003 não seria.
Ege Ersoz
11
Versão curta: os valores de ID serão usados ​​como números de pedido de compra. O cliente gera relatórios mensais e deseja poder informar rapidamente quantos pedidos de compra foram enviados naquele mês apenas olhando o número do pedido. Portanto, não podemos incrementá-lo em ~ 1000 (há uma manutenção semanal em que todos os servidores, incluindo o servidor DB, são reiniciados).
Ege Ersoz
3
Por que você não fornece a eles um relatório muito fácil que apenas usa ROW_NUMBER () OVER (PARTITION BY Month ORDER BY ID)? Novamente, o número de identificação não deve ter sentido, é uma maneira terrível de observar quantos pedidos foram feitos. E se você tiver um erro no seu código que exclua 1000 linhas ou reverta 275 transações ou 500 pedidos sejam legitimamente cancelados?
Aaron Bertrand
11
@Ege: "... diga quantos ... apenas olhando o número do pedido". Seus usuários vão se decepcionar. Os valores de identidade simplesmente não funcionam dessa maneira, nem você (ou eles) deve fazer tal suposição. Único? Sim. Consecutivo? Não. A maneira correta de contar os pedidos enviados durante um mês é ... contar o número de pedidos levantados durante esse mês, com base em algum campo de data [inalterável] em cada registro.
Phill W.
-4

Este é um problema do SQL Server. Tudo o que você pode fazer é reenviar a coluna.

exclua as entradas com o ID da coluna incorreto. Reseed a identidade da coluna. E então a próxima entrada possui o ID adequado.

Reseed Identity usando o seguinte comando sql: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 é o último ID correto

user190684
fonte
11
O que você quer dizer com "excluir as entradas"?
precisa saber é o seguinte
2
Hummm .. parece que excluir entradas pode levar à perda de dados.
Michael Green