GUIDを主キーとして使用する


32

通常、データベースの主キーとして自動インクリメントIDを使用します。GUIDを使用する利点を学ぼうとしています。私はこの記事を読みました:https : //betterexplained.com/articles/the-quick-guide-to-guids/

これらのGUIDは、アプリケーションレベルでオブジェクトを識別するために使用されることを理解しています。データベースレベルでプライマリキーとしても保存されますか。たとえば、次のクラスがあったとします:

public class Person
{
public GUID ID;
public string Name;
..

//Person Methods follow
}

メモリ内に新しい人物を作成し、その人物をデータベースに挿入したいとします。これをやってもいいですか:

Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

GUIDを主キーとする数百万の行を含むデータベースがあったとします。これは常に一意ですか?GUIDを正しく理解していますか?

以前にこの記事を読んだ:http : //enterprisecraftsmanship.com/2014/11/15/cqs-with-database-generated-ids/。GUIDと整数の間の幸せな媒体を主キーとして推奨するように見えるので、少し混乱します。

編集11/06/18

Guidsはintよりも自分の要件に適していると信じるようになりました。私は最近CQRSをより多く使用しており、GUIDはよりうまく適合しています。

一部の開発者は、GUIDをドメインモデルの文字列としてモデル化することに注意してください。例:https : //github.com/dotnet-architecture/eShopOnContainers/blob/dev/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/ Buyer.cs-この場合:IdentityGuidは文字列としてモデル化されたGUIDです。ここに記載されていること以外にこれを行う理由はありますか?カスタム値オブジェクトまたはGUIDを分散システムのエンティティ識別子として使用しますか?。GUIDを文字列としてモデル化するのは「通常」ですか、それともモデルとデータベースでGUIDとしてモデル化する必要がありますか?



7
一意であるとは限りませんが、衝突が発生する可能性はほとんどありません。stackoverflow.com/questions/1155008/how-unique-is-uuid/…–
icirellik

2
参照:UUID衝突
ブヨ

2
dba.stackexchange.com/questions/54690/…、および他の多くの質問も参照してください-このトピックはよく尋ねられ、回答され、議論されています。
グリーンストーンウォーカー

1
現在作業しているシステムはUUIDを使用しています。優れた特性は、テーブル内のレコードを識別するシーケンシャルIDとは対照的に、IDがレコードを一意に識別することです。
ジャスティン

回答:


41

GUIDは、定義により「Globally Unique IDentifiers」です。Javaには、UUID「Universally Unique IDentifiers」と呼ばれる同様の、しかしわずかに異なる概念があります。名前は、すべての実際の使用に対して交換可能です。

GUIDは、Microsoftがデータベースクラスタリングの動作を想定する方法の中心であり、時々接続されるソースからのデータを組み込む必要がある場合、データの衝突を防ぐのに役立ちます。

いくつかのPro-GUIDの事実:

  • GUIDはキーの衝突を防ぎます
  • GUIDは、ネットワーク、マシンなどの間でデータをマージするのに役立ちます。
  • SQL Serverは、インデックスの断片化を最小限に抑えるための半シーケンシャルGUIDをサポートしています(ref、いくつかの注意事項)

GUIDのあるUさ

  • それらは大きく、それぞれ16バイトです
  • それらは順不同であるため、IDでソートすることはできず、自動インクリメントIDで可能なように挿入順序を取得することを望みます
  • 特に小さなデータセット(ルックアップテーブルなど)での操作がより面倒です
  • 新しいGUIDの実装は、C#ライブラリよりもSQL Serverでより堅牢です(SQL ServerからシーケンシャルGUIDSを使用できますが、C#ではランダムです)

GUIDによりインデックスが大きくなるため、列のインデックス作成に必要なディスク領域のコストが高くなります。ランダムなGUIDはインデックスを断片化します。

異なるネットワークからのデータを同期するつもりがないことがわかっている場合、GUIDは価値がある以上のオーバーヘッドを運ぶ可能性があります。

時々接続されているクライアントからデータを取り込む必要がある場合、それらのクライアントにシーケンス範囲を設定することに依存するよりも、キーの衝突を防ぐためにより強力になります。


18
私の理解では、GUIDはUUIDと同義です。UUIDは標準名です。GUIDは、MicrosoftがRFC 4122に先立って作成したものです。
ジミージェームズ

13
「これらは順不同なので、IDでソートすることはできず、自動インクリメントIDのように挿入順序を取得することを望んでいます」極端な場合、より低いIDが後でディスクにコミットされる可能性はありますが、挿入タイムスタンプなどの有用なソートデータに頼る方がましです。Idはメモリアドレスのように扱う必要があります。すべてに1つがありますが、値自体は無意味です。最大でもタイブレーカーに使用してください。特に、大量の読み込みがある場合、挿入順序は保証されません。
時計仕掛けのミューズ

8
@CortAmmon ウィキペディアRFC 4122によると、それらは同義語です。P. Leach of MicrosoftはRFCの作成者の1人です。RFCが作成されて以来、この2つは同じだと思います。RFCから:「UUID(Universally Unique IDentifier)、別名GUID(Globally Unique IDentifier)」GUIDはMSによって作成されたものではないことに注意することも有用だと思います。彼らは、他の場所から採用された技術の新しい名前を作成しました。
ジミージェームズ

6
「SQL ServerはGUIDを処理するために最適化されているため、クエリのパフォーマンスに大きな影響はありません。-1十分に最適化されていない。私はすべてのPKがGUIDであるDBで作業していますが、これがパフォーマンス低下の主な原因の1つです。
アンディ

7
「SQL ServerにはGUIDを処理するための最適化機能があるため、クエリのパフォーマンスに大きな影響はありません」このステートメントは、他のデータ型が最適化されていないことを前提としています。データベースサーバーには、たとえば単純なint値を処理するための最適化機能もあります。GUID / UUIDは、4バイトのint値を使用するよりもかなり遅くなります。16バイトは、4バイトほど速くなることはありません。特に、ネイティブで最大4または8バイトを処理するマシンでは特にそうです。
アンドリューヘンレ

28

これは常に一意ですか?

常に?いいえ、常にではありません。それはビットの有限シーケンスです。

GUIDを主キーとする数百万の行を含むデータベースがあったとします。

何百万と何百万人、あなたはおそらく安全です。数百万、そして衝突可能性が大きくなります。ただし、良いニュースがあります。発生する頃には既にディスク領域が不足しています。

これだけでいいですか?

あなたはできる; それは完全に良い考えではありません。通常、ドメインモデルは乱数を生成しません。モデルへの入力である必要があります。

それを超えて、重複したメッセージを受け取る可能性のある信頼性の低いネットワークを扱う場合、決定論的に生成されたUUIDエンティティの重複を防ぎます。ただし、それぞれに新しい乱数を割り当てると、重複を特定するための作業が増えます。

RFC 4122の名前ベースのuuidの説明を参照してください

GUIDを文字列としてモデル化するのは「通常」ですか、それともモデルとデータベースでGUIDとしてモデル化する必要がありますか?

それほど重要ではないと思います。ドメインモデルのほとんどでは、識別子です。問い合わせる唯一のクエリは、他の識別子と同じかどうかです。通常、ドメインモデルはメモリ内の識別子の表現を参照しません。

GUIDがドメインに依存しない設定で "プリミティブタイプ"として利用できる場合、それを使用します。サポートコンテキストは、利用可能な適切な最適化を選択できます。

ただし、メモリとストレージの両方での識別子の表現は、実装で行う決定であるため、コードのフットプリントがそれに結合されるようにするための手順を実行する必要があります。決定は小さい- パルナス1972を参照。


20
「発生するまでにすでにディスク領域が不足しています」の場合は+1
w0051977

2
私は「のコンセプトを感じる決定論生成されたUUIDが」必須である(データボールト2を参照してください)
アルク

実際、他のデータに基づいてUUID / GUIDを再計算できることは、特に重複を検出するのに非常に役立ちます。メッセージを保存し、処理パイプラインを介してプッシュするメッセージ処理システムを構築しました。メッセージのハッシュを作成し、それをシステム全体の主キーとして使用しました。まさにそれだけで、スケールアウトする必要があるときにメッセージを特定するための多くの問題を解決しました。
ニュートピア

100万= 2 ^ 40。これにより、2 ^ 79ペアの衝突が発生します。GUIDには2 ^ 128ビットがあるため、チャンスは2 ^ 49分の1です。2つのレコードに同じGUIDを再利用するバグがあるか、衝突がないと誤って信じている可能性がはるかに高くなります。
gnasher729

私は歴史的な質問に戻ります。受け入れる前に; 私の編集を見ていただけますか?
w0051977

11

GUIDまたはUUID一意である可能性が非常に高い、それらがどのように生成されるかある、中央機関と通信することなく一意性を保証する安全な方法を提供します。

主キーとしてのGUIDの利点:

  • クラスターの異なるシャード間でデータをコピーでき、PKの衝突を心配する必要はありません。
  • レコードを挿入する前に、主キーを知ることができます。
  • 子レコードを挿入するためのトランザクションロジックを簡素化します。
  • 簡単に推測することはできません。

あなたが提供した例では:

Person p1 = new Person();
p1.ID = GUID.NewGUID();
PersonRepository.Insert(p1);

挿入時間の前にGUIDを指定すると、連続する子レコードを挿入するときにデータベースへのラウンドトリップを節約でき、同じトランザクションでそれらをコミットできます。

Person p2 = new Person();
p2.ParentID = p1.ID
PersonRepository.Insert(p2);

主キーとしてのGUIDへの悪影響:

  • これらは16バイトと大きいため、インデックスと外部キーが追加されると、より多くのスペースを消費します。
  • それらは本質的に乱数なので、うまくソートされません。
  • インデックスの使用は非常に、非常に、非常に悪いです。
  • たくさんの葉が動く。
  • 彼らは覚えにくいです。
  • 彼らは言葉遣いが難しいです。
  • URLを読みにくくすることができます。

アプリケーションにシャーディングやクラスタリングの必要がない場合は、intやbigintなどの小さくて単純なデータ型を使用することをお勧めします。

多くのデータベースには、GUIDによって引き起こされるストレージの問題を軽減しようとする独自の内部実装があり、SQL Serverにはインデックスのより良い使用を可能にするUUIDの順序付けを支援する機能newsequentialidがあり、一般にパフォーマンス特性が優れています。

さらに、アプリケーションで作業するテスター、ユーザー、または開発者の観点から、GUIDでIDを使用すると、コミュニケーションが大幅に向上します。電話でGUIDを読む必要があると想像してください。

最終的に、大規模なクラスタリングまたはURLの難読化が要件でない限り、自動インクリメントIDに固執する方が実用的です。


1
考慮すべきことの1つは、UUIDのタイプに応じて、生成されるマシンを識別するために使用される可能性のある情報が含まれていることです。純粋なランダムバリアントは、十分なエントロピーなしで衝突する可能性が高くなります。これは、URIで使用する前に考慮する必要があります。
ジミージェームズ

ただし、URLでプライマリキーを公開することはできません。外部システムに安全なデータが漏洩しないように、より適切な方法を使用する必要があります
。s– icirellik

1
もう1つのユースケースがあります。シーケンスのロックがボトルネックである重い挿入OLTPデータベース。私のOracle DBAの友人によると、これは見た目ほど珍しいことではなく、そのために大規模なものやクラスターさえ必要ではないということです。•最後に、長所と短所を比較検討し(UUIDの長所/短所を、一部のポスターのようにUUIDに固有ではない長所/短所と混同しないでください)、測定します。
ミラビロス

1
newsequentialidを使用する場合、IDを取得するためにdbに移動する必要があります(ID intを使用する場合など)。ここでの利点は何ですか。
-w0051977

1
@mirabilos明確にするために、恐ろしいと言うと、行ごとに数分かかる挿入物ができてしまいました。最初は問題ありませんでしたが、数万の行があった後、横に非常に速くなりました。明らかでない場合、数万行が非常に小さなテーブルです。
ジミージェームズ

4

いいえ、主キーとしてGUIDを使用しないでください。私は実際にそのようなDBを扱っていますが、それらはパフォーマンスの問題の主な原因の1つです。

余分な12バイトはすぐに追加されます。覚えておいてください、ほとんどのPKは他のテーブルのFKであり、テーブルの3つのFKだけで各行に48バイトが追加されます。それがテーブルとインデックスに追加されます。また、ディスクI / Oが増加します。これらの余分な12バイトは、読み取りおよび書き込みが必要です。

また、シーケンシャルGUIDを使用しておらず、PKがクラスター化されている場合(これはデフォルトで行われます)、SQLはデータのページ全体を時々移動して、より適切な「スポット」に絞り込む必要があります。大量の挿入、更新、削除を行う高度なトランザクションデータベースの場合、物事はすぐに行き詰まります。

同期などのために何らかの一意の識別子が必要な場合は、guid列を追加します。PKにしないでください。


4
Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

これは、GUIDを使用する最も重要な理由です。

コードが永続層を認識または通信せずに一意のIDを作成できるという事実は大きな利点です。

サーバー、PC電話、ラップトップ、オフラインデバイスなどで生成したPersonオブジェクトが、世界中のすべてのサーバーで一意であることに注意してください。

任意の種類のデータベースrdbまたはno-sql、ファイルに貼り付け、任意のWebサービスに送信するか、不要なものとしてすぐに破棄できます。

いいえ、衝突は発生しません。

はい、インデックスの調整が必要になる可能性があるため、挿入は少し遅くなります。

はい、intよりも大きいです。

  • 編集。仕上げる前に撃たなければなりませんでした。

多くの人がauto inc intについて強く感じていることを知っています。これはDBAの議論のあるトピックです

しかし、私は本当に優れたGUIDがどれほど強力かを述べることはできません。どのアプリケーションでも、デフォルトでGUIDを使用する必要があります。

auto inc intには多くの多くの欠陥があります

  • No-Sql分散データベースを使用します。他のすべてのインスタンスと話をして、次の番号が何であるかを知ることはできません。

  • メッセージキューシステムを使用します。データベースにアクセスする前にIDが必要です

  • 保存する前に、いくつかのアイテムを作成して編集しています。データベースにアクセスする前に、それぞれにIDが必要です

  • 行を削除して再挿入します。auto inc idをカウントアップして実行しないようにしてください!

  • 今年行った注文数をすべてのユーザーに公開したくない場合

  • 匿名化されたデータを実稼働環境から移動して、関係をテストして維持します。ただし、既存のすべてのテストデータを削除しないでください。

  • シングルテナント製品をマルチテナントデータベースにマージしたいが、誰もが注文56を持っている。

  • 持続するが一時的なオブジェクトを作成します。(不完全な注文)再び、もはや存在しないものですべてのintを使い切ってはいけません。

リストは無限であり、それらはすべて人々に常に起こる本当の問題です。FK列がわずかに大きいためにディスク領域が不足するのとは異なります

最後に、intの大きな問題は、それらを使い果たすことです !!! 理論上はいけない、負荷があります。しかし、実際には、人々はそれらを意味のない乱数のように扱わないため、あなたはそうします。彼らは次のようなことをします

  • ああ、顧客に私たちが新しいと思ってほしくありません。10,000から開始

  • 大量のデータをインポートする必要があったので、シードを1mに増やしたので、インポートされたものがわかります

  • カテゴリのデータが必要です。すべての期間は次の100万から始まるため、最初の数字をマジックナンバーとして使用できます

  • すべてのデータを削除して、新しいIDで再インポートしました。はい、監査ログも。

  • 複合キーであるこの番号を、この他のIDとして使用します


1
この答えに事実上問題はありませんが、実際にアプリケーションが衝突に遭遇しなくても、理論的には可能だという警告を明確にしたいと思います。(または、おそらく45以上のエクサバイトデータベースは、私が思っていたよりも普及しています...)。「最も重要な理由」という言葉はやや強いと思いますが、これは私が最も役立つと思うものです。
-BurnsBA

2
その可能性が高い自動車株式会社のintはGUIDよりも衝突する
ユアン・

4
「すべてのアプリケーションでデフォルトでGUIDを使用する必要があります」の場合は-1 それは依存します™。また、他の人が示したように、GUID / UUIDは一意であるとは限りません。
マックスヴァーノン

3
「依存する」答えは無用です。intの方が優れている奇妙なアプリケーションがあることは確かです。ただし、アプリケーションがそれらの1つではない可能性があります。GUIDは、入手できる最もユニークなものです
Ewan

2
GUIDが優れている奇妙なアプリケーションがいくつかあると思います。ユニークは考慮すべき最も重要なものではありません。intの「欠陥」は非常に誇張されており、guidの多くの欠点を考慮していません。
アンディ

2

これらのGUIDは、アプリケーションレベルでオブジェクトを識別するために使用されることを理解しています。データベースレベルでプライマリキーとしても保存されますか。

そこで立ち止まって、すぐに考え直してください。

データベースの主キーには、ビジネス上の意味は決してありません。定義上は意味がありません。

したがって、GUIDをビジネスキーとして追加し、通常のプライマリキー(通常はlong int)をデータベースプライマリキーとして追加します。一意性を確保するために、常に一意のインデックスをGUIDに配置できます。

それはもちろんデータベース理論の話ですが、それは良い習慣でもあります。主キーにビジネス上の意味があるデータベースを扱ってきました(たとえば、ある従業員が従業員番号、顧客番号などとして使用することでデータベースリソースを節約しようと考えていた)が、常にトラブルにつながります。


1
これは、整数主キーを使用してアプリケーション層からクエリを実行することとどのように違いますか?その時点で、アプリケーション層でオブジェクトを識別するためにも使用されています。アプリケーション層からデータベース内のオブジェクトを識別する方法が必要です。
icirellik

@icirellik主キーは、親レコードと子レコードなどをリンクするために、データベースによる内部使用を目的としています。アプリケーションロジックで使用するためのものではなく、製品番号や名前などのビジネスIDを使用します。
-jwenting

2

データベースで生成された自動インクリメントの主キー(PK)を常に使用します。

GUID / UUIDの代わりに自動インクリメントを使用する理由

  • GUID(UUID)は一意ではないため、キーの衝突を防ぐことはできません。多くのソースから生成されるため、一意にする方法はありません。
  • GUIDは、非常に長く、整数ではないPKおよびFKカラムを使用したすでに時間のかかるマージプロセスを大幅に増加させるため、マージに役立ちません。ほとんどのPKには、同じサイズのキーが少なくとも2つある他のテーブルが少なくとも1つあることに注意してください。それは、独自のPKと最初のテーブルに戻るFKです。すべてをマージで解決する必要があります。

しかし、シャード、クラスターなどをどのように処理するのでしょうか?

  • 各シャード/クラスター/データベース/それ自体の自動インクリメントキーを管理するものを識別する個別の列で構成される複数列PKを作成します。例えば...

クラスタ化されたテーブルの3列PKは...

 DB | SH | KEY     |
----|----|---------|
 01 | 01 | 1234567 |

しかし、どうでしょう...?

  • データベースへの複数のトリップ-ほとんどのアプリケーションは、データベースに挿入されるまで作成されているレコードを一意に識別する必要はありません。そのスレッド/セッション/すべてが一度に1つだけで動作しているからです。アプリケーションで実際にこの機能が必要な場合は、データベースに送信されないアプリケーション生成の一時PK 使用します。その後、データベースが挿入されたときに、データベースに独自の自動インクリメントPKを行に追加させます。挿入では一時的なPKが使用され、更新および削除ではデータベースによって割り当てられた永続的なPKが使用されます。

  • パフォーマンス-可能な場合、GUID(37)と整数(10)の要素ごとの値が非常に大きいため、コンピューターは他のどの整数よりもはるかに高速に単純な整数を処理できます。また、GUIDの各文字は、CPUで操作するために最初に数字に変換する必要があることにも注意してください。

主キーの一般的な誤用 PKには、テーブルの行を完全に一意に識別するという1つの目的しかありません。それ以外のものは、ありふれた誤用です。

不足しているレコードの検出

  • 不足しているレコードは、PKを見ても検出できません。少なくともデータ品質の確保を試みるためにQAを祝福してください。ただし、彼らとプログラマーは、現代のデータベースシステムのキーの割り当て方法を理解していないため、多くの場合、自動インクリメントPKの欠落した数値はデータの欠落を意味するという誤解につながります。それはそうではありませ ...
  • パフォーマンスのために、データベースシステムは、ストレージ内の実際のデータベースへのアクセスを最小限に抑えるために、 'シーケンス'(バッチ、範囲)で数値のブロックを割り当てます。これらの数字のシーケンスのサイズは、多くの場合DBAの制御下にありますが、テーブルごとに調整できない場合があります。
  • 重要なポイントは...これらのシーケンスの未使用の番号がデータベースに返されることはないため、PK番号には常にギャップがあることです。
  • なぜあなたが尋ねる未使用の番号があるのでしょうか?さまざまなデータベースメンテナンスアクションによってシーケンスが中止される可能性があるためです。これらは、再起動、テーブルのバルクリロード、バックアップからのある種の復元、その他の操作などです。

仕分け

  • PKによる並べ替えは、ほとんどの人が作成された順序で行をリストし、それが時刻に対応していると考えるため、非常にエラーを起こしやすいです。ほとんどの場合、必ずしも必要ではありません。
  • データベースエンジンは最大のパフォーマンスが得られるように最適化されており、短い単純なトランザクションを挿入するために、実行時間の長い複雑なトランザクションの結果の挿入を遅らせることを意味する場合があります。

唯一の一意の列がデータベースで作成された自動インクリメントの主キーであるようなテーブルスキーマについてはどう思いますか?特に、外部キーはないが、主キーがいくつかの関連テーブルの外部キーであるテーブルの場合はどうでしょうか?
-RibaldEddie

これらの線に沿って、答えにさらに多くを追加しました。元々の答えは、私が吊るしているAndroid SEアプリのために不完全でした。アプリの大幅な書き直しが開発中だと思います。
-DocSalvager

あなたのビューでは、自動インクリメントの主キーを除いて同一の行をテーブルにいくつでも含めることができますか?
-RibaldEddie

@RibaldEddie-DBが許すように設計されている限り...絶対に。削除は簡単です。シナリオが発生した場合、ソフトウェアで修正されるバグと見なし、いずれかの行を削除します。しかし、はるかに一般的なケースは、わずかに異なるデータを持つ同じものの2つのレコードであるため、それらをマージする必要があります。1つのレコードで列が空で、もう1つのレコードに値がある場合、選択は明らかであり、自動化できます。多くの場合、datetimestampは自動マージの調停に使用できます。一部の複製では、ビジネスルールに基づいてマージを完了して検証する必要があります。
DocSalvager

1

何でもそうですが、これを行うには長所と短所があります。

いいもの:

  1. キーは常に同じ長さです(非常に大きなデータベースは非常に大きなキーを持つことができます)

  2. 一意性はほぼ保証されています-別のシステムから生成している場合、および/またはデータベースから最後のIDを読み取っていない場合でも

悪い人:

  1. 前述のように、より大きなインデックスとデータストア。

  2. IDで注文することはできません。別のもので注文する必要があります。インデックスが増えると、おそらく効率が低下します。

  3. それらは人間が読めるものではありません。一般に、整数は人にとって解析、記憶、および入力が簡単です。複数の結合テーブルにまたがるWHERE句でIDとしてGUIDを使用すると、頭が溶ける可能性があります。

すべての場合と同様に、必要に応じてそれらを使用し、独断的ではありません。多くの場合、整数の自動インクリメントが優れており、GUIDが優れている場合があります。


0

はい、GUIDを主キーとして使用できます。マイナス面は、インデックスのサイズと急速な断片化です。

データベース全体(クラスタなど)で一意性が必要な場合を除き、整数が推奨されます。


GUIDジェネレーターは、同じGUIDを複数回生成する場合がありますが、これには欠陥があります。それらが粒度であるかどうかは、主にクロックの刻みの間隔に依存します。たとえば、クロックベースのジェネレーターは100ミリ秒ごとにのみティックし、そのマシンでその100ミリ秒以内に要求される2つのGUIDは同一になります。ほとんどの場合、これを回避する方法がありますが、多くのGUIDジェネレーターは、IPアドレスやMACアドレス、タイムスタンプをまったく使用せずに動作します。
-jwenting

0

この問題に対する私の見解は次のとおりです。解決策は、GUIDとint値の間の中間の家で、両方を最大限に活用します。

このクラスは、Comb GUIDに似た疑似ランダム(ただし時間とともに増加する)Id値を生成します

主な利点は、サーバーで生成される自動インクリメント値(往復が必要)を使用するのではなく、クライアントでID値を生成できることです。値が重複するリスクはほとんどありません。

生成された値は、GUIDに16バイトではなく8バイトのみを使用し、特定のデータベースの並べ替え順序に依存しません(GUIDのSql Serverなど)。値を拡張して、符号なしの長距離全体を使用することもできますが、これにより、符号付き整数型のみを持つデータベースまたはその他のデータリポジトリで問題が発生します。

public static class LongIdGenerator
{
    // set the start date to an appropriate value for your implementation 
    // DO NOT change this once any application that uses this functionality is live, otherwise existing Id values will lose their implied date
    private static readonly DateTime PeriodStartDate = new DateTime(2017, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    private static readonly DateTime PeriodEndDate = PeriodStartDate.AddYears(100);
    private static readonly long PeriodStartTicks = PeriodStartDate.Ticks;
    private static readonly long PeriodEndTicks = PeriodEndDate.Ticks;
    private static readonly long TotalPeriodTicks = PeriodEndTicks - PeriodStartTicks;

    // ensures that generated Ids are always positve
    private const long SEQUENCE_PART_PERMUTATIONS = 0x7FFFFFFFFFFF; 

    private static readonly Random Random = new Random();

    private static readonly object Lock = new object();
    private static long _lastSequencePart;

    public static long GetNewId()
    {
        var sequencePart = GetSequenceValueForDateTime(DateTime.UtcNow);

        // extra check, just in case we manage to call GetNewId() twice before enough ticks have passed to increment the sequence 
        lock (Lock)
        {
            if (sequencePart <= _lastSequencePart)
                sequencePart = _lastSequencePart + 1;

            _lastSequencePart = sequencePart;
        }

        // shift so that the sequence part fills the most significant 6 bytes of the result value
        sequencePart = (sequencePart << 16);

        // randomize the lowest 2 bytes of the result, just in case two different client PCs call GetNewId() at exactly the same time
        var randomPart = Random.Next() & 0xFFFF;

        return sequencePart + randomPart;
    }

    // used if you want to generate an Id value for a historic time point (within the start and end dates)
    // there are no checks, compared to calls to GetNewId(), but the chances of colliding values are still almost zero
    public static long GetIdForDateTime(DateTime dt)
    {
        if (dt < PeriodStartDate || dt > PeriodStartDate)
            throw new ArgumentException($"value must be in the range {PeriodStartDate:dd MMM yyyy} - {PeriodEndDate:dd MMM yyyy}");

        var sequencePart = GetSequenceValueForDateTime(dt.ToUniversalTime());
        var randomPart = Random.Next() & 0xFFFF;
        return ( sequencePart << 16 ) + randomPart;
    }

    // Get a 6 byte sequence value from the specified date time - startDate => 0 --> endDate => 0x7FFFFFFFFFFF
    // For a 100 year time period, 1 unit of the sequence corresponds to about 0.022 ms
    private static long GetSequenceValueForDateTime(DateTime dt)
    {
        var ticksFromStart = dt.ToUniversalTime().Ticks - PeriodStartTicks;
        var proportionOfPeriod = (decimal)ticksFromStart / TotalPeriodTicks;
        var result = proportionOfPeriod * SEQUENCE_PART_PERMUTATIONS;
        return (long)result;
    }

    public static DateTime GetDateTimeForId(long value)
    {
        // strip off the random part - the two lowest bytes
        var timePart = value >> 16;
        var proportionOfTotalPeriod = (decimal) timePart / SEQUENCE_PART_PERMUTATIONS;
        var ticks = (long)(proportionOfTotalPeriod * TotalPeriodTicks);
        var result = PeriodStartDate.AddTicks(ticks);
        return result;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.