UUIDとGUIDの違い:実際に何が異なるのか

GUIDはUUIDのMicrosoftでの呼び名です。実務で出会う違いは、テキストの表記、16バイトとして保存したときのバイト順、そしてSQL Serverでの並び順です。

ツールで試す: GUID生成 UUID v4生成

短い答えだけが必要なら、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での出力は次のとおりです。

指定子出力
Nf47ac10b58cc4372a5670e02b2c3d479
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()
生成方法ランダム、バージョン4Windowsの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形式をカバーできます。

よくある質問

GUIDとUUIDは同じものですか?

はい。GUIDはMicrosoftがUUID形式を指して使う名前です。どちらも128ビットの値で、Guid.NewGuid()は標準的なランダムのバージョン4 UUIDを生成します。

データベースから読み出したGUIDが違って見えるのはなぜですか?

最初の3グループのバイトが入れ替わっています。.NETのToByteArray()とSQL Serverはこれらのグループをリトルエンディアンで格納しますが、他のほとんどの言語は16バイトをRFCのビッグエンディアン順で読みます。明示的に変換するか、テキストやネイティブのUUID型で保存してください。

GUIDは大文字と小文字のどちらで書くべきですか?

どちらも有効です。RFC 4122は出力を小文字に、入力を大文字小文字を区別しない扱いにすることを推奨しており、WindowsのツールやSQL Serverは大文字で表示します。文字列ではなく、パースした値どうしを比較してください。

UUID v7を使えばSQL Serverのインデックスは順番どおりになりますか?

なりません。SQL Serverはuniqueidentifierの末尾6バイトを最初に比較し、v7の値ではそのバイトはランダムです。NEWSEQUENTIALID()はSQL Serverにとって順序があり、v7は左から右へバイトを比較するデータベースにとって順序があります。
ツールで試す: GUID生成 UUID v4生成