Lacunes inattendues dans la colonne IDENTITY

18

J'essaie de générer des numéros de bons de commande uniques qui commencent à 1 et incrémentent par 1. J'ai une table PONumber créée à l'aide de ce 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)    
);

Et une procédure stockée créée à l'aide de ce script:

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

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

Au moment de la création, cela fonctionne bien. Lorsque la procédure stockée s'exécute, elle démarre au nombre souhaité et incrémente de 1.

Ce qui est étrange, c'est que si j'arrête ou met en veille prolongée mon ordinateur, la prochaine fois que la procédure s'exécute, la séquence avance de près de 1000.

Voir les résultats ci-dessous:

Numéros de bon de commande

Vous pouvez voir que le nombre est passé de 8 à 1002!

  • Pourquoi cela arrive-t-il?
  • Comment puis-je m'assurer que les chiffres ne sont pas sautés comme ça?
  • Tout ce dont j'ai besoin, c'est que SQL génère des nombres qui sont:
    • a) Garantie unique.
    • b) incrémenter du montant souhaité.

J'avoue que je ne suis pas un expert SQL. Dois-je mal comprendre ce que fait SCOPE_IDENTITY ()? Dois-je utiliser une approche différente? J'ai examiné les séquences dans SQL 2012+, mais Microsoft dit qu'elles ne sont pas garanties d'être uniques par défaut.

Ege Ersoz
la source

Réponses:

25

Il s'agit d'un problème connu et attendu - la façon dont les colonnes IDENTITY sont gérées par SQL Server a changé dans SQL Server 2012 ( certains antécédents ); par défaut, il mettra en cache 1000 valeurs et si vous redémarrez SQL Server, redémarrez le serveur, basculez, etc. il devra rejeter ces 1000 valeurs, car il n'aura pas de moyen fiable de savoir combien d'entre elles étaient réellement Publié. Ceci est documenté ici . Il existe un indicateur de trace qui modifie ce comportement de telle sorte que chaque affectation IDENTITY est enregistrée *, empêchant ces lacunes spécifiques (mais pas les lacunes des annulations ou des suppressions); cependant, il est important de noter que cela peut être assez coûteux en termes de performances, donc je ne vais même pas mentionner ici l'indicateur de trace spécifique.

* (Personnellement, je pense que c'est un problème technique qui pourrait être résolu différemment, mais comme je n'écris pas le moteur, je ne peux pas changer cela.)

Pour être clair sur le fonctionnement de l'IDENTITÉ et de la SÉQUENCE:

  • Aucun des deux n'est garanti comme étant unique (vous devez appliquer cela au niveau de la table, en utilisant une clé primaire ou une contrainte unique)
  • Aucun des deux n'est garanti sans faille (toute annulation ou suppression, par exemple, produira un écart, malgré ce problème spécifique)

L'unicité est facile à appliquer. Éviter les lacunes ne l'est pas. Vous devez déterminer à quel point il est important pour vous d'éviter ces lacunes (en théorie, vous ne devriez pas vous soucier des lacunes, car les valeurs IDENTITY / SEQUENCE doivent être des clés de substitution sans signification). Si c'est très important, vous ne devriez pas utiliser l'une ou l'autre implémentation, mais plutôt lancer votre propre générateur de séquence sérialisable (voir quelques idées ici , ici et ici ) - notez simplement que cela tuera la concurrence.

Beaucoup d'histoire sur ce "problème":

Aaron Bertrand
la source
Cette réponse (à l'exception de la partie "indicateur de trace") s'applique également à la plupart des autres bases de données SQL (celles qui ont des séquences de toute façon).
mustaccio
Merci d'avoir répondu. L'unicité est l'exigence la plus importante. Les lacunes ne sont pas un gros problème, tant qu'elles ne sont pas importantes. Par exemple, passer de 1 à 4 serait acceptable, mais de 4 à 1003 ne le serait pas.
Ege Ersoz
1
Version courte: les valeurs d'ID seront utilisées comme numéros de bon de commande. Le client génère des rapports mensuels et souhaite pouvoir dire rapidement combien de bons de commande ont été soumis ce mois-ci simplement en regardant le numéro de bon de commande. Nous ne pouvons donc pas l'incrémenter de ~ 1000 (il y a une maintenance hebdomadaire où tous les serveurs, y compris le serveur DB, sont redémarrés).
Ege Ersoz
3
Pourquoi ne leur donnez-vous pas un rapport très simple qui utilise simplement ROW_NUMBER () OVER (PARTITION BY MOIS ORDER BY ID)? Encore une fois, le numéro d'identification ne devrait pas avoir de sens, c'est une façon terrible de voir combien de commandes ont été prises. Que faire si vous avez un bug dans votre code qui supprime 1000 lignes ou annule 275 transactions, ou 500 commandes sont légitimement annulées?
Aaron Bertrand
1
@Ege: "... dites combien ... rien qu'en regardant le numéro de bon de commande". Vos utilisateurs vont être déçus. Les valeurs d'identité ne fonctionnent tout simplement pas de cette façon, et vous (ou elles) ne devez pas faire de telles hypothèses. Unique? Oui. Consécutif? Non. La façon correcte de compter les bons de commande soumis au cours d'un mois est ... de compter le nombre de bons de commande levés au cours de ce mois, en fonction d'un champ de date [inaltérable] dans chaque enregistrement.
Phill W.
-4

C'est un problème de SQL Server. Tout ce que vous pouvez faire est de réamorcer la colonne.

supprimez les entrées avec un identifiant de colonne incorrect. Réamorcez l'identité de la colonne. Et puis l'entrée suivante a une ID appropriée.

Rétablir l'identité à l'aide de la commande sql suivante: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)- 9 est le dernier ID correct

user190684
la source
1
Que voulez-vous dire par «supprimer les entrées»?
ypercubeᵀᴹ
2
Hmmm .. la suppression d'entrées pourrait entraîner une perte de données.
Michael Green