web-dev-qa-db-ja.com

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

私は通常、データベースの主キーとして自動インクリメント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と整数の中間の幸せなメディアを推奨しているように見えるため、少し混乱します。

編集18/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を分散内のエンティティ識別子として使用しますsystem? 。GUID=を文字列としてモデル化するのは「通常」ですか、またはモデルとデータベースでGUIDとしてモデル化する必要がありますか? ?

32
w0051977

GUIDは、定義上、「グローバルに一意の識別子」です。 Java UUIDs "Universally Unique IDentifiers"と呼ばれる)には、似ているが少し異なる概念があります。名前はすべての実用的な用途で交換可能です。

GUIDは、Microsoftがデータベースのクラスタリングをどのように機能させるかを想定した中心的な機能であり、接続されているソースからのデータを組み込む必要がある場合、GUIDはデータの衝突を防ぐのに役立ちます。

いくつかのプロGUIDの事実:

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

GUIDによる不明瞭さ

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

GUIDを使用するとインデックスが大きくなるため、列のインデックスを作成するためのディスク容量コストは高くなります。ランダムGUIDはインデックスを断片化します。

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

時々接続しているクライアントからデータを取り込む必要がある場合、それらのクライアントのシーケンス範囲の設定に依存するよりも、キーの衝突を防ぐためにはるかに堅牢である可能性があります。

42
Berin Loritsch

これは常にユニークですか?

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

たとえば、GUIDを主キーとして持つ何百万もの行を含むデータベースがあったとします。

何百万と何百万、あなたはおそらく安全です。数百万、そして 衝突の可能性 が重要になります。ただし、朗報があります。それまでに、ディスク領域が不足しています。

これだけでいいですか?

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

それ以上に、重複したメッセージを受け取る可能性がある信頼性の低いネットワークを扱っている場合、決定的に生成されたUUIDは重複したエンティティを持つことからあなたを守ります。ただし、それぞれに新しい乱数を割り当てると、重複を特定するためにさらに多くの作業が必要になります。

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

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

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

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

ただし、認識すべきことは、メモリとストレージの両方での識別子の表現は、実装で行う決定であるため、それに関連付けられたコードのフットプリントが決定は小さいです Parnas 1972 を参照してください。

28
VoiceOfUnreason

GUIDまたは [〜#〜] uuid [〜#〜] は、それらがどのように生成され、そのため、おそらく nique になります中央当局と通信することなく、一意性を保証する安全な方法を提供します。

主キーとしての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には、関数 newsequentialid があり、UUIDの順序付けに役立ち、インデックスの使用法が向上し、一般にパフォーマンスが向上します特性。

さらに、テスター、ユーザー、またはアプリケーションで作業する開発者の観点から、GUIDでIDを使用すると、通信が大幅に向上します。GUID電話で。

結局のところ、大規模なクラスタリングやURLの難読化が要件でない限り、自動インクリメントIDを使用するほうが実用的です。

11
icirellik
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がどれほど優れているかを十分に強く述べることはできません。どのアプリケーションでもdefaultでGUIDを使用する必要があります。

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

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

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

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

  • 行を削除して再挿入したい。自動インクIDを数え上げて実行しないようにしてください。

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

  • 匿名化されたデータを本番環境からテストに移動して、関係を維持します。ただし、既存のテストデータをすべて削除するわけではありません。

  • シングルテナント製品をマルチテナントデータベースにマージしたいのですが、全員が注文56を持っています。

  • 永続的で一時的なオブジェクトを作成します。 (不完全な注文)繰り返しますが、もはや存在しないものですべてのintを使い果たしてはいけません。

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

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

  • ああ、私たちが新しいと思ってほしくない。 10,000から

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

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

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

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

4
Ewan

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

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

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

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

4
Andy

常に、データベースが生成し、自動インクリメントする主キー(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による並べ替えは、行が作成された順にリストされ、それが時刻に対応すると考えられるため、エラーが発生しやすくなります。ほとんどの場合、必須ではありません。
  • データベースエンジンは最大のパフォーマンスが得られるように最適化されているため、複雑なトランザクションを長時間実行すると、いわば「アウトオブターン」の単純なトランザクションを挿入するために、結果の挿入が遅れる可能性があります。
3
DocSalvager

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

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

データベースの主キーには、ビジネス上の意味はありません。定義上、無意味であるべきです。

したがって、GUIDをビジネスキーとして追加し、通常の主キー(通常はlong int)をデータベースの主キーとして追加します。常に一意のインデックスをGUID一意性を確保します。

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

2
jwenting

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

良い:

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

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

悪い:

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

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

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

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

2
Phil S

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

データベース全体(クラスターなど)で一意性が必要でない限り、整数が推奨されます。

0
paparazzo

これがこの問題に対する私の見解です-解決策はGUIDとint値の間の中間の家であり、両方の長所を取り入れています。

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

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

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

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;
    }
}
0
Peregrine