CombGuid。 .netアプリケーションでのSQL ServerフレンドリなGUID値の生成

テーブルの主キーとしてuuidを使用すると、多くの利点があります。その1つは、データベースサーバーにアクセスせずに、クライアントアプリケーションで作成されたオブジェクトの識別子を個別に取得できることです。 しかし、主キーとしてuuidを使用することには欠点があります。クライアントアプリケーションによって生成されたguidはSQLサーバーにとって「フレンドリー」ではないため、新しいレコードを追加するときにオーバーヘッドが発生する可能性があります。 挿入操作のコストが増加する可能性があるのは、SQLサーバーが通常、 bツリーと呼ばれる構造を使用して、主キーが指定されているテーブルを格納するためです。 新しいレコードをテーブルに追加するとき、SQLサーバーは、プライマリキーによる並べ替えに従って、挿入されたレコードを配置するシートを検索します。 擬似ランダムuuid生成アルゴリズムを使用すると、新しいレコードのソート順もランダムになり、レコードを配置するシートが完全にいっぱいになる可能性があります。 このような場合、SQLサーバーはシートを2つに分割し、そのシートにつながるbツリーブランチを再構築する必要があります。 新しいレコードを追加するときにクラスターインデックスを絶えず再構築する必要性をSQLサーバーに直面させないために、主キー値を昇順で生成できます。 昇順でGUIDを生成する1つの方法は、生成されたGUIDのソート順を現在の時間にバインドすることです。 この方法で生成された識別子は、CombGuidと呼ばれることが多く、通常のGuidのような疑似ランダム部分と時間制限のある文字列という2つの半分から構築されているという事実を暗示しています。



SQL Serverがuuidを比較する方法


SQL Serverは、.uidとは異なる方法でuuid値をソートします。 比較は、右から左へのバイトグループで実行されます。 バイトグループ内では、比較はすでに左から右に行われています。 (バイトグループは「-」で区切られたシーケンスです。)2つのuuid値を比較する場合、

@ u1 = '206AEBE7-ABF0-47A8-8AA5-6FDDF39B9E4F'

そして

@ u2 = '0F8257A1-B40C-4DA0-8A37-8BBC55183CAE'の場合、出力は@ u2> @ u1になります。前述のように、SQLサーバーは右端のバイトグループ(6FDDF39B9E4F <8BBC55183CAE)から比較を開始するためです。 より技術的に言えば、バイト9から15は降順で、データベース内のuuidソート順序に最大の影響を与えます。



MagnumライブラリでのCombGuidの実装


このプロジェクトでは、 Magnumライブラリを使用します。その一部は、時間制限のあるGUIDを作成する単一のGenerate()メソッドを持つ静的なCombGuidクラスです。 Magnumは、 GitHubでホストされるオープンソースライブラリです。 私はあまり面倒ではなく、Guid作成メソッドの実装がこのライブラリでどのように見えるかを見ました。



public static class CombGuid { static readonly DateTime _baseDate = new DateTime(1900, 1, 1); public static Guid Generate() { byte[] guidArray = Guid.NewGuid().ToByteArray(); DateTime now = DateTime.Now; // Get the days and milliseconds which will be used to build the byte string var days = new TimeSpan(now.Ticks - _baseDate.Ticks); TimeSpan msecs = now.TimeOfDay; // Convert to a byte array // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 byte[] daysArray = BitConverter.GetBytes(days.Days); byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds/3.333333)); // Reverse the bytes to match SQL Servers ordering Array.Reverse(daysArray); Array.Reverse(msecsArray); // Copy the bytes into the guid Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2); Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4); return new Guid(guidArray); } }
      
      





アルゴリズムは非常に簡単です。

9〜10バイトで、1900年1月1日から経過した日数がエンコードされます。 経過日数が2バイトに収まらない2079年にソースを再構築することを忘れてはなりません。 11-15バイトは、何らかの理由で3.333333で除算して、1日の始まりからミリ秒をエンコードするために使用されます。 コード内のコメントは、この操作がSQLサーバーにタイムスタンプを保存する精度が1/300秒であるという事実に関連していることを示しています。 かなり奇妙な解決策です。uuidを生成するプロセスでは、SQLサーバーがタイムスタンプを保存する方法は関係ないので、uuidの作成にのみミリ秒を使用します。 私はこの質問を少しグーグルで検索しましたが、Magnumライブラリの作成者であるChris PattersonがNhibernateからCombGuid生成コードをコピーしたことだけに気付きました。 ここでわかるように、GenerateCombメソッドには同じコードが含まれています。 公平には、ミリ秒を3.333333で割ってもアルゴリズムの動作に特別な影響はないことに注意してください。これは単なる追加のオプションのステップです。



Guid vs CombGuid。 データベースの挿入速度を比較する


最後に、サーバーSQLテーブルにレコードを挿入するコンテキストで、Guid.NewGuid()メソッドによって生成されたuuidが、CombGuid.Generate()を介して作成された兄弟よりも遅いのと比較して、それがすべての目的に達しました。

テストのために、SQLサーバー上にテーブルを作成し、これらのテーブルに100,000行を挿入する2つのスクリプトを作成しました。 最初のスクリプトは、CombGuid.Generate()メソッドを使用して作成されたIDを持つデータベース行に挿入し、2番目のスクリプトはGuid.NewGuid()メソッドを使用して挿入します。



テストスクリプトの一部。

 USE [CombIdTest] GO --   DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; CREATE TABLE [dbo].[CombId]( [ID] [uniqueidentifier] NOT NULL, [Value] [varchar](4000) NOT NULL, CONSTRAINT [PK_CombId] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO --      begin transaction insert into CombId Values ('5cb31d3d-3793-428e-beb0-a2e4047e255c','somevalue'); insert into CombId Values ('1e905fa1-e4d4-4a2c-a185-a2e4047e255d','somevalue'); --  99998  insert commit transaction
      
      





挿入を実行する前に、バッファキャッシュがフラッシュされ、トランザクションログへの呼び出し回数を減らすために、挿入自体が1つのトランザクションで実行されます。 各スクリプトは3回実行され、クライアント統計の「合計実行時間」パラメーターが実行時間として使用されました。 測定はMSSQL Server 2012で行われました。



測定結果(ミリ秒単位)。

1 2 3 平均
コンビッド 2795 2882 2860 2845,667
ランダムガイド 3164 3129 3111 3134,667


CombGuidを含むレコードを挿入するスクリプトの利点は、「通常の」uuidを使用したスクリプトに比べて10パーセント強です。 CombGuidを使用すると、テーブルのサイズにもプラスの効果がありました。そのサイズは、ほぼ1.5倍小さく、3.75 MB対5.25 MBでした。



さて、最後にいくつかの質問


データベースの主キーとして何を使用しますか?

uuidまたは類似のバイト構造を使用する場合、どのように生成しますか?




All Articles