IDENTITY列の予期しないギャップ


16

1から始まり1ずつ増加する一意の注文番号を生成しようとしています。このスクリプトを使用してPONumberテーブルを作成しています。

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)    
);

このスクリプトを使用して作成されたストアドプロシージャ:

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

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

作成時に、これは正常に機能します。ストアドプロシージャが実行されると、目的の数から始まり、1ずつ増加します。

奇妙なことは、コンピューターをシャットダウンまたは休止状態にした場合、次にプロシージャを実行すると、シーケンスがほぼ1000ずつ進んだことです。

以下の結果を参照してください。

発注番号

数字が8から1002にジャンプしたことがわかります。

  • なんでこんなことが起こっているの?
  • そのように番号がスキップされないようにするにはどうすればよいですか?
  • 必要なのは、SQLが次の数値を生成することだけです。
    • a)一意の保証。
    • b)必要な量だけ増加します。

私はSQLの専門家ではありません。SCOPE_IDENTITY()の機能を誤解していますか?別のアプローチを使用する必要がありますか?SQL 2012+でシーケンスを調べましたが、Microsoftはデフォルトでシーケンスが一意であるとは限らないと言っています。

回答:


23

これは既知の予想される問題です-SQL ServerによるIDENTITY列の管理方法は、SQL Server 2012で変更されました(背景)。デフォルトでは1000個の値をキャッシュします。SQLServerの再起動、サーバーの再起動、フェイルオーバーなどを行う場合、それらの1000個の値を破棄する必要があります。発行済み。これはここに文書化されています。すべてのIDENTITY割り当てがログに記録されるようにこの動作を変更するトレースフラグがあり、それらの特定のギャップを防止します(ただし、ロールバックまたは削除によるギャップは防止しません)。ただし、これはパフォーマンスの点で非常にコストがかかる可能性があることに注意することが重要です。したがって、ここでは特定のトレースフラグについても言及しません。

* (個人的には、これは別の方法で解決できる技術的な問題だと思いますが、エンジンを記述していないので、それを変更することはできません。)

IDENTITYとSEQUENCEの仕組みを明確にするには:

  • どちらも一意であることが保証されていません(主キーまたは一意制約を使用して、テーブルレベルで強制する必要があります)
  • どちらもギャップがないことが保証されていません(たとえば、ロールバックまたは削除はギャップを生成しますが、この特定の問題にもかかわらず)

一意性を強制するのは簡単です。ギャップを避けることはできません。これらのギャップを避けることの重要性を判断する必要があります(理論的には、IDENTITY / SEQUENCEの値は無意味な代理キーであるため、ギャップをまったく気にする必要はありません)。非常に重要な場合は、どちらの実装も使用せずに、独自のシリアル化可能なシーケンスジェネレーターをロールする必要があります(こちらこちらこちらこちらのアイデアをご覧ください)。

この「問題」に関する多くの背景:


この回答(「トレースフラグ」の部分を除く)は、他のほとんどのSQLデータベース(とにかくシーケンスを持つもの)にも適用されます。
mustaccio

答えてくれてありがとう。一意性は、最も重要な要件の1つです。ギャップが大きくない限り、ギャップは大した問題ではありません。たとえば、1から4に変更することはできますが、4から1003に変更することはできません。
エーゲエルソス

1
ショートバージョン:ID値は注文番号として使用されます。顧客は月次レポートを実行し、PO番号を見るだけで、その月に送信されたPOの数をすばやく確認できるようにしたいと考えています。そのため、これを1000まで増やすことはできません(DBサーバーを含むすべてのサーバーを再起動する毎週のメンテナンスがあります)。
エーゲErsoz

3
ROW_NUMBER()OVER(PARTITION BY Month ORDER BY ID)を使用するだけの非常に簡単なレポートを提供してみませんか?繰り返しになりますが、ID番号は無意味である必要があります。これは、注文がいくつ行われたかを見るためのひどい方法です。コードに1000行を削除したり、275件のトランザクションをロールバックしたり、500件の注文が正当にキャンセルされたりするバグがある場合はどうなりますか?
アーロンバートランド

1
@Ege:「... PO番号を見るだけで、その数を教えてください」。ユーザーは失望するでしょう。アイデンティティ値は単にそのように機能しないだけでなく、あなた(または彼ら)がそのような仮定をするべきではありません。ユニークですか?はい。連続?いいえ。1か月に送信されたPOをカウントする正しい方法は、各レコードの[変更不可]日付フィールドに基づいて、その月に発生したPOの数をカウントすることです。
フィルW.

-4

これはSQL Serverの問題です。できることは、列を再シードすることだけです。

間違った列IDを持つエントリを削除します。列IDを再シードします。そして、次のエントリには適切なIDがあります。

次のsqlコマンドを使用した再シードID: DBCC CHECKIDENT ('YOUR_TABLE_NAME', RESEED, 9)-9は最後の正しいIDです


1
「エントリを削除する」とはどういう意味ですか?
ypercubeᵀᴹ

2
Hmmm ..エントリを削除すると、データが失われる可能性があります。
マイケルグリーン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.