大きなID値を避ける理由


17

私たちは、ユーザーがまだアクセスできないWebアプリケーションに取り組んでいます。上司は、テーブルに100未満のレコードしかないにもかかわらず、新しく作成されたレコードが10000を超えるIDを取得することに気付きました。彼女は、何らかの理由でWebインターフェースが実際のレコードよりも100倍以上の一時的なレコードを作成(および削除)し、これによりリリース後数か月以内に範囲外になる可能性があると考えました。

彼女はIDインフレーションの原因について正しいとは思わない(これに答えることができる同僚は休暇中なので、私たちは確実に知らない)が、彼女がそうであると仮定しよう。彼女は、bigintカラムを使用するのは嫌いで、IDカラムの自動インクリメントを停止し、最初の「未使用」整数を選択してそれをIDとして使用するサーバー側コードを記述してほしいと言いました。

私はコンピューターサイエンスの大学院生であり、実務経験がほとんどなく、開発者の若手として働いています。彼女は、当社のすべてのデータベースを管理し、それらのほとんどを設計した長年の経験を持っています。私が考える BIGINT IDがの恐れることは何もない、とDBMSの機能を模倣することはアンチパターンの匂いということを、彼女はこの場合には正しくないということ。しかし、私はまだ自分の判断を信用していません。

各ポジションの賛否両論は何ですか?bigintを使用すると、どのような悪いことが起こる可能性がありますか?ホイールの自動インクリメント機能を再発明することの危険性は何ですか?どちらよりも優れた3番目のソリューションはありますか?IDの額面価格の上昇を避けたい理由は何でしょうか?私は実用的な理由についても興味があります-bigint IDは理論的には機能するかもしれませんが、実際には頭痛の種になりますか?

アプリケーションが非常に大量のデータを処理することは想定されていません。今後数年以内に実際の記録が10,000件に達するとは思わない。

違いがある場合は、Microsoft SQLサーバーを使用しています。アプリケーションはC#で記述され、Linq to SQLを使用します。

更新

ありがとう、私は既存の答えとコメントがおもしろいと思った。しかし、あなたは私の質問を誤解したのではないかと思うので、彼らは私が知りたいことを含んでいます。

高いIDの本当の理由についてはあまり心配していません。自分で見つけられない場合は、別の質問をすることができます。私が興味を持っているのは、この場合の決定プロセスを理解することです。このため、アプリケーションが1日あたり1000レコードを書き込み、そのうちの9999レコードを削除すると想定してください。これは事実ではないと確信していますが、これは上司が要求したときに信じていたものです。したがって、これらの仮想的な状況では、bigintを使用するか、IDを割り当てる独自のコードを作成することの長所と短所は何ですか(すでに削除されたレコードのIDを再利用し、ギャップがないようにする)?

実際の理由については、別のデータベースからデータをインポートするコードを書いたことが、後の移行がある程度できるという概念の証明として、私が強く疑っています。私の同僚は、実際にインポート中に数千のレコードを作成し、後でそれらを削除したと思います。これが実際にそうだったかどうかを確認する必要がありますが、もしそうなら、アクションの必要さえありません。


でSM Ahasanハビービス記事を参照してください codeproject.com/Tips/668042/...
RLF

明確にできますか?新しいIDは10000を超える値を取得するだけですか?または、新しいIDに10000のギャップがあるのですか?そして、将来のアプリのライフに必要とされるIDの数は?
user2338816 14

1
最初の未使用のIDを見つけることに関しては、Bill Karwinの本「SQL Antipatterns」に正確にそれに関する章があります。はい、それは確かにアンチパターンとして見ることができます!
トーマスパドロン-マッカーシー14

回答:


24

コードを見ずに、何が起こっているのかを断定的に言うのはかなり難しいです。ただし、ほとんどのIDENTITY場合、値はキャッシュされているため、SQL Serverの再起動後に値にギャップが生じます。いくつかの適切な回答とその情報については、https://stackoverflow.com/questions/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-serverを参照してください

単純なINTフィールドには、最大2,147,483,647の値を保持できます。実際には、-2,147,483,648でID値を開始し、32ビットの値をすべて指定できます。40億の異なる値。使用する値が不足することを非常に疑います。アプリケーション追加された実際の行ごとに1,000個の値を消費していると仮定すると、IDENTITY値を0 から開始してINTを使用していると仮定すると、6か月で毎日12,000近くの行を作成してIDを使い果たす必要があります。BIGINTを使用している場合、1日あたり12,000行を書き込み、行ごとに1,000個の「値」を消費すると、値がなくなるまで2,100万世紀待たなければなりません。

以上のことを述べBIGINTましたが、IDフィールドのデータ型として使用する場合、間違いなく何も問題はありません。これにより、すべての意図と目的、使用する値の無限の供給が得られます。INTとBIGINTのパフォーマンスの違いは、最新の64ビットハードウェアでは実質的に存在せず、NEWID()GUIDの生成に使用するインスタンスよりも非常に望ましいものです。

ID列の独自の値を管理したい場合は、キーテーブルを作成し、この質問に対する回答に示されている方法のいずれかを使用して、それを行うかなり防弾の方法を提供できます: キーテーブルへの同時アクセスの処理SQL Serverのデッドロック

もう1つのオプションは、SQL Server 2012+を使用している場合、SEQUENCEオブジェクトを使用して列のID値を取得することです。ただし、値をキャッシュしないようにシーケンスを構成する必要があります。例えば:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;

上司の「高い」数字に対する否定的な認識に答えて、それはどのような違いを生むのでしょうか?を使用してINTフィールドを使用すると仮定するとIDENTITY、実際にIDENTITYat 2147483647を開始し、で値を「インクリメント」できます-1。それがある場合、これは32ビットの数は4バイト、どんなにあるので、使用するメモリの消費量、パフォーマンス、またはディスクの空き容量に絶対に違いはありませんでしょう021474836470バイナリでは00000000000000000000000000000000、32ビットの符号付きINTフィールドに格納されます。 214748364701111111111111111111111111111111-両方の数値は、メモリとディスクの両方で正確に同じ量のスペースを必要とし、処理するには両方とも正確に同じ量のCPU操作が必要です。キーフィールドに格納されている実際の数を把握するよりも、アプリケーションコードを正しく設計することがはるかに重要です。

BIGINT(a)a などの大容量ID列を使用するか、(b)IDのギャップを防ぐために独自のソリューションを展開するかの長所と短所について質問しました。これらの懸念に答えるには:

  1. BIGINTINT問題の列のデータ型としてではなく。を使用するにBIGINTは、列自体にディスク上とメモリ内の両方の2倍のストレージが必要です。列が関連するテーブルのプライマリキーインデックスである場合、テーブルにアタッチされているすべての非クラスター化インデックスにも列が格納され、ページあたり80行しか格納できません。非常に多数の行があるテーブルの場合、これは明らかに、この例では、テーブルの読み取りと書き込みに必要なI / Oが任意の行数に対して2倍になることを意味します。確かに、これは非常に極端な例です-単一または列と単一の列で構成される行がある場合、使用するかどうかにかかわらず、ページごとに単一の行を取得することになりますBIGINT値を、の2倍のサイズで、INTメモリ内とディスク上の両方に格納します。SQL Serverは、データを8KBページでディスクに保存します。「ページ」あたりの「行」の数は、各行の「幅」に依存します。そのため、たとえば、10列のテーブルがあり、各列がである場合、INT1ページあたりおよそ160行を格納できます。それらの列が代わりにBIGINTINTBIGINTNCHAR(4000)INTBIGINT。このシナリオでは、それほど大きな違いはありません。

  2. ID列のギャップを防ぐために、独自のシナリオを展開します。使用する「次の」ID値を決定しても、テーブルで発生する他のアクションと競合しないようにコードを記述する必要があります。SELECT TOP(1) [ID] FROM [schema].[table]素朴な線に沿って何かが思い浮かびます。テーブルに新しい行を同時に書き込もうとする複数のアクターがある場合はどうなりますか?2つのアクターが同じ値を簡単に取得でき、書き込み競合が発生しました。この問題を回避するには、テーブルへのアクセスをシリアル化する必要があり、パフォーマンスが低下します。この問題について書かれた多くの記事がありました。そのトピックの検索を実行するのは読者に任せます。

ここでの結論は、要件を理解し、アプリケーションの同時実行要件とともに行数と行幅の両方を適切に見積もる必要があるということです。いつものように、It Depends™。


4
+1ですが、BIGINTのスペース要件は破棄しません。ディスク上のスペースではなく、メモリ内のI / Oとスペースが無駄になります。データ圧縮を使用してこれの多くを相殺することができるので、20億を超えるまでBIGINTタイプの打撃を実際に感じることはありません。理想的には、彼らは単に問題を修正するだけです(私はそれをバグと呼ぶことをheします)-人々はギャップを気にするべきではありませんが、人々は1日に15回サーバーを再起動するべきではありませんが、両方のシナリオがあります非常に普及しており、しばしばタンデムで。
アーロンバートランド

3
非常に有効なポイント、アーロン、いつものように。とにかくINTを使用する傾向があります。BIGINTは、膨大な数の行を予期している場合を除き、ほぼ完全に過剰であるためです。
マックスヴァーノン14

ID列のBIGINTデータ型は、同時に数十万以上のメモリがメモリにない限り、メモリに大きな影響を与えません。それでも、総行サイズのごく一部になる可能性があります。
user2338816 14

2
@ user2338816がポイントです-テーブルが大きくなると、メモリ内に多くのものが存在します。また、通常、ID列はクラスタリングキーであるため、すべてのインデックスの1行ごとに4バイト余分になります。すべてのケースで問題になりますか?いいえ。無視する必要がありますか?絶対違う。手遅れになるまで、スケーラビリティについての情報を提供する人はいないようです。
アーロンバートランド

3
あなたがあればけれども行うに必要となる可能性があるという正当な期待を持っているbigintあなたは、事前にではなく数十億行を持つテーブルにして、これを追加する必要がより決定するために自分自身を多分感謝します。
マーティンスミス14

6

主なタスクは、現在の値がそれほど高い理由を見つけることです。

SQL 2012より前のバージョンのSQL Serverの最も合理的な説明は、テストデータベースについて話していると仮定すると、負荷テストの後にクリーンアップが行われたということです。

SQL2012以降、最も可能性の高い理由は、SQLエンジンの数回の再起動によるものです(最初のリンクMaxで説明)。

ギャップがテストシナリオに起因する場合、私の観点から心配する理由はありません。ただし、安全のために、アプリケーションの通常の使用中、およびエンジンの再起動の前後にID値を確認します。

MSは、両方の選択肢(トレースフラグ272または新しいSEQUENCEオブジェクト)がパフォーマンスに影響を与える可能性があるとMSが述べているのは「面白い」です。

MSの次の「改善」をカバーするために安全な側にいるためだけに、INTではなくBIGINTを使用するのが最善のソリューションかもしれません...


私はおそらく質問を間違った方法で言いましたが、私はその原因を見つけることにあまり興味がありません。再び表示されないもの(テスト実行の結果)か、アプリケーションの設計上の不適切な決定である可能性が高く、データベース外で解決できます。ポイントは、経験豊富なDBAが高いIDを悪いと考える理由、または独自のID管理を展開するよりも悪いと考える理由を理解することでした。
rumtscho 14

2

Rumtscho、1日あたり1000行しか作成しない場合、決定することはほとんどありません。INTフィールドでINTデータ型を使用し、それで完了です。簡単な計算では、アプリに30年のライフサイクルを(ほとんどありませんが)与えれば、1日あたり200,000行あり、INTデータ型の正の数値範囲内に収まる可能性があります。

BigIntの使用はやり過ぎです。また、アプリやデータにODBC経由でアクセスする場合(ExcelやMS Accessなどに持ち込むなど)、問題を引き起こす可能性があります。BigintはほとんどのODBCドライバーをデスクトップアプリに変換しません。

GUIDSに関しては、余分なディスク領域と余分なI / Oを除いて、設計上、シーケンシャルではないという大きな問題があるため、それらが並べ替えられたインデックスの一部である場合、すべての挿入が行われると推測できますインデックスを再ソートする必要があります。-ジム


GUIDについての良い点は、NEWSEQUENTIALID()を使用しない限り-私はまだ同意しますが、この質問で明らかに使用する大きな理由はありません。
マックスヴァーノン14

1

使用される値の間にギャップがありますか?または、開始値は10.000であり、それ以降はすべて1が加算されますか?顧客に番号が与えられる場合、最初の番号がゼロより大きくなる場合があります。たとえば、1500とすると、顧客はシステムが「新しい」ことを認識しません。

smallintの代わりにbigintを使用することの欠点は、bigintが「より多くのディスク容量」を使用するため、ディスクの読み取り時にすべてのディスクの読み取りディスクブロックが少なくなることです。行スペースが小さい場合、これは欠点になる可能性がありますが、そうでない場合はそれほど重要ではありません。また、一度に多くのリソースを照会しておらず、適切なインデックスがあれば、それは重要ではありません。

また、他の応答で述べたように、インデックスが不足することを心配する場合は、大金持ちのビジネスがない限り、smallintは処理できます。「IDを回復する」メカニズムを発明するのは費用がかかり、ソフトウェアに障害点と複雑さを追加します。

よろしく


2
OPには、サービスの再起動時にギャップが見られます。これはこの問題のためです。また、smallintは、後で修正するために必要な作業にとって、短期的には良いトレードオフだとは思いません。
アーロンバートランド

@AaronBertrandは実際、他の人がこの可能性を示唆したときにこれを誤解したのではないかと心配しています。これが大きな数字の原因ではないと確信していますが、それがあったとしても、私は原因を見つけようとはしていませんでしたが、提案された解決策に対して賛否両論があります。詳細については私の更新を参照してください。
rumtscho 14

@rumtschoは、実際にこの答えがあなたの質問に直接対処していなくても良い点を強調しています。「「IDを回復する」メカニズムを発明するのは高価で、ソフトウェアに障害点と複雑さを追加します。
ドクターJ 14

@DoktorJ私はあなたに同意します。私は答えを支持した人でした:)誤解を解消したかったので、最初のコメントを残しました。
rumtscho 14

1

私はあなたの上司だった場合でしょうあなたが概説2つのシナリオのそれぞれについて、最も予想外に高いのId値の理由に興味がある...私はそれを参照してください方法:

  1. 事前のテストでID値が増加した場合-予想されるレコード数に関する他のコメントも、より小さなキータイプの提案を促します。率直に言って、テーブルの現在の意図された使用のためにテストが文字外であった場合、シーケンスをリセットし、既存のレコードに番号を付け直すことが可能かどうかも検討します(ほとんどの場合、この過剰を考慮します-「依存する」)。

  2. テーブルに書き込まれたレコードの大部分が、代わりに2つのテーブルを使用することを検討する傾向があるとすぐに削除される場合、レコードが長期間保持されない一時テーブルと、永続的に作成するレコードのみが保持される別のテーブル。繰り返しますが、長期レコードの数に対するあなたの期待は、キー列により小さいタイプを使用することを示唆しており、1日に数個のレコードを使用しても、あるテーブルから別のテーブルにレコードを「移動」するパフォーマンスの問題はほとんどありません1。それはあなたのシナリオではないのではないかと思いますが、ショッピングWebサイトがBasket / BasketItemを維持することを好み、実際に注文が出されるとデータがOrder / OrderItemセットに移動されることを想像してください。

要約すると; 私の意見では、BIGINTは必ずしも恐れられる必要はありませんが、多くのシナリオでは率直に言って不必要に大きいです。テーブルが大きくならない場合、タイプの選択が過剰になっていることに気付かないでしょう...しかし、数百万行と多くのFK列を持つテーブルがある場合、それらは小さいかもしれないが、BIGINTタイプはより控えめに選択されていました(キー列だけでなく、すべての比較キー列、保持するすべてのバックアップなども考慮してください!)。ディスク容量は必ずしも安価ではありません(管理された場所のSANディスクを検討してください-つまり、ディスク容量はレンタルされます)。

本質的には、時々ではなく、常にデータタイプの選択を慎重に検討することを主張しています。使用パターンを常に正しく予測することはできませんが、「より大きければ大きいほど良い」と常に仮定してから、ルールとしてより良い決定を下すと思います。一般に、必要かつ合理的な値の範囲を含むことができる最小の型を選択し、値が近い将来にその型に収まる可能性が高いと思われる場合は、INT、SMALLINT、TINYINTを考慮します。ただし、IDENTITY列では小さい型を使用することはほとんどありませんが、キー値を手動で設定するルックアップテーブルでは喜んで使用できます。

最後に、人々が使用する技術は、彼らの期待と答えに大きく影響します。一部のツールは、プロセスごとのIDの範囲を事前に予約するなどして、範囲にギャップを生じる可能性が高くなります。対照的に、@ DocSalvagerは、上司の視点を反映しているように見える徹底した監査可能なシーケンスを提案しています。私は個人的にそのレベルの権限をまったく要求しませんでしたが、IDが連続的であり、一般にギャップがないという一般的なルールは、サポート状況や問題分析において私にとって非常に役立つことがよくありました。


1

bigintを使用するか、IDを割り当てる独自のコードを記述することの長所と短所は何でしょうか(ギャップが存在しないように、既に削除されたレコードのIDを再利用する方法で)?

bigintアイデンティティとして使用し、ギャップのある生活:

  • それはすべて組み込みの機能です
  • すぐに動作することを確認できます
  • intまだ約200万日分のデータが提供されるため、スペースが無駄になります。さらにページを読み書きする必要があります。インデックスが深くなる場合があります。(これらのボリュームでは、これは重要な懸念事項ではありませんが)。
  • 代理キー列は意味がないため、ギャップは問題ありません。ユーザーに表示され、ギャップが重要であると解釈される場合は、間違っています。

独自のロール:

  • 開発チームはすべての開発およびバグ修正作業を永久に行うことになります。
  • テールの隙間も中央の隙間も埋めたいですか?議論する決定を設計します。
  • 書き込みごとに強力なロックを発行して、同時プロセスが同じ新しいIDを取得しないようにするか、事後の競合を解決する必要があります。
  • 最悪の場合、rowid = 1が削除された場合、ギャップを埋めるためにテーブルのすべての行を更新する必要があります。これにより、並行性とパフォーマンス、カスケード外部キーの更新などすべてが打撃を受けます。
  • 怠け者または熱心なギャップ埋め?これが起こっている間に並行性はどうなりますか?
  • write =追加のロードの前に新しいIDを読み取る必要があります。
  • 効率的なギャップ検索を行うには、id列にインデックスが必要です。

0

PKのINTの上限しきい値に達することが本当に心配な場合は、GUIDの使用を検討してください。はい、16バイト対4バイトであることは知っていますが、ディスクは安価です。

ここだ良い書き込みアップ長所と短所のは。


4
これは解決策であるため+1ですが、「ディスクが安い」という理由でMaxの答えに対するアーロンのコメントを参照してください。慎重にオプションを比較検討することなくGUIDを使用する理由ではありません。
ジャックダグラス14

1
ここではSQL Serverのインデックスとアーキテクチャの専門家ではなく、開発者からの優れた書き込みアップだ:sqlskills.com/blogs/kimberly/disk-space-is-cheap
アーロン・ベルトラン

ああ、もちろん、NEWID()からのページ分割に注意してください
Max Vernon 14

1
私の上司は、高く見えるという理由でのみ高い価値に反対するようです。この質問がより多くの異論を示してくれることを期待していますが、これが彼女の主な議論の1つである場合、おそらく彼女はGUIDに対してさらに否定的に反応するでしょう。
rumtscho 14

1
@rumtscho上司に、サロゲート番号は無意味な番号であり(番号の「サイズ」は無関係です)、シーケンスのギャップは自然であり、ほとんど避けられないことを伝えます。
アーロンバートランド

0

RDBMSプライマリキー(通常は 'ID'という名前の列)
RDBMSの自動インクリメント列(フィールド)のギャップは回避できません。それらは、一意のPKを作成することを主な目的としています。パフォーマンスのために、主要製品はこれらをバッチで割り当てます。そのため、さまざまな通常動作のグリッチの自動回復メカニズムにより、数値が未使用のままになる可能性があります。これは正常です。

切れ目のないシーケンス
、多くの場合、ユーザが期待されているようなあなたは切れ目のないシーケンス番号を必要とする、それがプログラムで割り当てられているとすべきである別の列である必要がありません PKこと。したがって、これらの1000レコードはすべて、その列に同じ番号を持つことができます。

ユーザーが連続したシーケンスを必要とするのはなぜですか?
欠落しているシーケンス番号は、あらゆる種類の監査で発見されたエラーの最も基本的な兆候です。この「Bookkeeping-101」の原則はどこにでもあります。ただし、手作業で維持される少数のレコードで機能するものは、データベース内の非常に多数のレコードに適用すると深刻な問題を抱えています...

関連しないレコードのキー値を再利用すると、データベースが無効になります
「最初の未使用整数」を使用すると、将来のある時点で、元のレコードとは無関係のレコードで番号が再利用される可能性が生じます。そのため、事実の正確な表現としてデータベースが信頼できなくなります。これが、自動インクリメントメカニズムが値を決して再利用しないように意図的に設計されている主な理由です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.