UUIDとGUIDの違い:実際に何が異なるのか
GUIDはUUIDのMicrosoftでの呼び名です。実務で出会う違いは、テキストの表記、16バイトとして保存したときのバイト順、そしてSQL Serverでの並び順です。
短い答えだけが必要なら、GUIDとUUIDは同じ種類の値です。128ビットの識別子で、通常は32桁の16進数を5つのグループに分けて書きます。.NETのGuid.NewGuid()も、JavaScriptのcrypto.randomUUID()も、ランダムなバージョン4のUUIDを生成します。それでも相互運用の問題は起こり、その原因は3つあります。値をテキストとしてどう書くか、16バイトをどの順に並べるか、そしてSQL Serverがどう比較するかです。
1つの標準に2つの名前
UUIDはApolloのNetwork Computing Systemに由来し、OSFのDistributed Computing Environment(DCE)に引き継がれました。MicrosoftはCOMにDCEの設計を採用し、その値をglobally unique identifier(GUID)と呼びました。その後IETFが2005年7月にRFC 4122としてこの形式を公開し、2024年5月には、UUID v6、v7、v8を追加したRFC 9562に置き換えられました。どちらのRFCも、UUIDがGUIDとも呼ばれることを明記しています。
この命名の歴史は、あるフィールドに刻まれています。4番目のグループの先頭にあるバリアントビットは、そのグループが8、9、a、bで始まるRFCのUUIDと、cまたはdで始まるMicrosoftの後方互換用に予約された範囲とを区別します。現在のWindowsと.NETが作るGUIDはRFCのバリアントを使っているため、実際には両者を区別できません。
テキスト表記
.NETのGuid.ToString()は書式指定子を受け取ります。同じ値に対する.NET 8での出力は次のとおりです。
| 指定子 | 出力 |
|---|---|
N | f47ac10b58cc4372a5670e02b2c3d479 |
D(既定) | f47ac10b-58cc-4372-a567-0e02b2c3d479 |
B | {f47ac10b-58cc-4372-a567-0e02b2c3d479} |
P | (f47ac10b-58cc-4372-a567-0e02b2c3d479) |
X | {0xf47ac10b,0x58cc,0x4372,{0xa5,0x67,0x0e,0x02,0xb2,0xc3,0xd4,0x79}} |
どれも小文字です。{F47AC10B-58CC-4372-A567-0E02B2C3D479}のような波括弧付きの大文字の書き方は、Windowsのレジストリ、COMのクラスID、Visual Studioのプロジェクトファイルに由来し、SQL Serverもuniqueidentifierの値を大文字で表示します。RFC 4122は出力を小文字に、入力を大文字小文字を区別しない扱いにするよう求めており、Guid.Parseは表のすべての形式を受け付けます。
実務上のルールは、GUIDを文字列として比較しないことです。"{F47AC10B-...}"と"f47ac10b-..."は、文字列としては別物でも値としては同じです。両方をパースするか、保存や比較の前に波括弧なしの小文字に正規化してください。
バイト順:本当にハマる違い
16バイトとして保存する場合、RFCのUUIDはビッグエンディアンで、バイトは16進数の桁と同じ順に並びます。MicrosoftのGUID構造体は違います。32ビット整数1つ、16ビット整数2つ、8個の単独のバイトとして宣言されており、リトルエンディアンのマシンでは3つの整数がバイトを逆順にして格納されます。Guid.ToByteArray()はまさにそのレイアウトを返し、new Guid(byte[])もそのレイアウトを前提にしています。
Text: f47ac10b-58cc-4372-a567-0e02b2c3d479
RFC bytes: f4 7a c1 0b 58 cc 43 72 a5 67 0e 02 b2 c3 d4 79
ToByteArray(): 0b c1 7a f4 cc 58 72 43 a5 67 0e 02 b2 c3 d4 79
末尾の8バイトは同じで、最初の3グループが反転しています。SQL Serverも同じミックスエンディアンのレイアウトを使っており、uniqueidentifierをbinary(16)に変換するとToByteArray()と同じバイト列になります。
バグが起きるのは、一方があるレイアウトでバイトを書き、もう一方が別のレイアウトで読むときです。.NETのサービスがToByteArray()の結果をBINARY(16)カラムやバイナリメッセージに書き込み、Java、Go、PythonのサービスがそのバイトをRFCのUUIDとして読むと、0bc17af4-cc58-7243-a567-0e02b2c3d479が得られます。値は一意なままなので何も失敗しませんが、2つのシステム間の照合は決して一致しません。バージョンの桁も移動しており、この例では入れ替わった値がバージョン7を名乗っています。
レイアウトを1つに決め、境界で明示的に変換してください。
- .NET 8以降:
guid.ToByteArray(bigEndian: true)とnew Guid(bytes, bigEndian: true)で、RFCの順序で読み書きできます。 - Python:
uuid.UUID(bytes_le=data)でMicrosoftのレイアウトを読み、u.bytes_leで書き出せます。 - どの環境でも:生のバイト列ではなく、テキストかネイティブのUUID型で保存すれば、この問題はそもそも起きません。
SQL Serverでのuniqueidentifierの並び順
SQL ServerはGUIDを、テキストの見た目どおりには並べません。末尾の6バイトのグループを最初に比較し、次に4番目のグループ、続いて3番目、2番目、1番目のグループを比較します。しかも最初の3グループの中では、右端のバイトが最も重みを持ちます。次の一覧はuniqueidentifierの昇順で、同じ比較を実装している.NETのSqlGuid型で並べたものです。
01000000-0000-0000-0000-000000000000
ff000000-0000-0000-0000-000000000000
00000001-0000-0000-0000-000000000000
00000000-0100-0000-0000-000000000000
00000000-0001-0000-0000-000000000000
00000000-0000-0100-0000-000000000000
00000000-0000-0001-0000-000000000000
00000000-0000-0000-0001-000000000000
00000000-0000-0000-0100-000000000000
00000000-0000-0000-0000-000000000002
00000000-0000-0000-0000-010000000000
同じ値を.NETのSystem.Guidとして、あるいは任意の言語で文字列として並べると、順序が変わります。00000000-0000-0000-0000-000000000002が先頭に、ff000000-0000-0000-0000-000000000000が末尾に来ます。ここから2つの影響が生じます。
- キーセットページネーションで行が抜けたり重複したりすることがあります。アプリケーションが自分でGUIDを並べ、その後SQL Serverに
WHERE id > @lastで行を要求する場合です。並べ替えも取得も、データベースに任せてください。 - SQL ServerではUUID v7は役に立ちません。v7の値はタイムスタンプで始まりますが、SQL Serverはランダムな末尾を先に比較します。そのため.NET 9の
Guid.CreateVersion7()で作ったものも含め、v7のキーはv4のキーと同様に、uniqueidentifierインデックスのランダムな位置に入ります。
NEWIDとNEWSEQUENTIALIDの違い
NEWID() | NEWSEQUENTIALID() | |
|---|---|---|
| 生成方法 | ランダム、バージョン4 | WindowsのUuidCreateSequentialによる時刻ベースのUUIDを、バイトを並べ替えて使う |
| 使える場所 | T-SQLのどこでも | uniqueidentifierカラムのDEFAULT制約の中だけ |
| 順序 | ランダム | そのマシン上で、SQL Serverの並び順に沿って増加。Windowsの再起動後に小さい値から再開することがある |
| 予測可能性 | なし | あり:次の値を推測できる |
| インデックスでの挙動 | INSERTがどこにでも入り、ページ分割を起こす | INSERTはインデックスの末尾に入る |
NEWSEQUENTIALID()が存在するのは、ランダムなキーがクラスター化インデックスを断片化させるからです。バイトの並べ替えこそが、SQL Serverの独特な比較のもとで値を増加させる仕掛けであり、テキストとして並べても作成順が再現されない理由でもあります。値を推測できるため、Microsoftのドキュメントはプライバシーが重要な場面での使用を勧めていません。こうしたIDはURLに出さないでください。アプリケーション側でキーを生成する場合、SQL Serverと同じように並べるには時刻ベースのバイトを最後のグループに置く必要があり、これはUUID v7とは正反対のやり方です。
どちらも生成する
値が同じなので、UUID v4の生成ツールはそのままGUIDの生成ツールでもあります。GUID生成ツールは、レジストリ風の大文字・波括弧付きでランダムなバージョン4の値を生成し、UUID v4生成ツールは同じ種類の値を小文字のRFC形式で生成します。どちらも波括弧、大文字小文字、ハイフンを切り替えられるので、上で紹介したN、D、B形式をカバーできます。