UUID v1 / v4 / v7 の使い分け — RFC 9562 に基づく実用ガイド
UUID(Universally Unique Identifier)は IETF の RFC 9562(2024年5月、旧 RFC 4122 を廃止・更新)で定義される 128 ビット識別子です。長らく使われてきた v1 / v4 に加え、2024 年の改訂でv6 / v7 / v8 が正式に標準化されました。本記事では各版のビット構造と、どのケースでどれを選ぶべきかを整理します。
UUID の基本構造
UUIDは常に128ビット(16オクテット)で、標準の表記は 8-4-4-4-12 の 16 進数(例: 550e8400-e29b-41d4-a716-446655440000)です。
RFC 9562 は構造を次の領域に分割します:
- version: 上位側の4ビット(13番目のニブル)。1〜8 がバージョン番号
- variant: その次の2〜3ビット。ほぼすべて "10xx" (RFC 9562 変種)
- 残りの116〜118ビット: バージョンごとに意味が変わる
つまりUUIDの種類は13番目のニブルで即座に判別できます。550e8400-e29b-41d4-... の "4" が v4、"7" なら v7 です。
v1 — 時刻とMACアドレス
v1 は 1582年10月15日(グレゴリオ暦の基準日)からの 100ns 単位のタイムスタンプ 60 ビットと、ノード識別子(通常はMACアドレス)48ビットで構成されます。
time-low : 32bit ←低位ビット
time-mid : 16bit
time-hi : 12bit + version(4bit)
clock-seq: 14bit + variant(2bit)
node : 48bit ←MACアドレスまたは乱数
利点: 時刻順にソート可能(ただしビット順序が直感に反するため並べ替え注意)。分散システムでの衝突が極めて少ない。
欠点: MACアドレスが埋め込まれるためプライバシー漏洩リスクがある。RFC 9562 では乱数ノードによる代替も許可されていますが、レガシー実装に注意。
v4 — 完全ランダム
v4 は 122ビットがすべて乱数(version 4ビットと variant 2ビットを除く)です。最もシンプルで、現在最も広く使われています。
衝突確率: 122ビットの乱数空間(約 5.3 × 10³⁶)は極めて広く、誕生日のパラドックスを考慮しても、10億個を生成した場合の衝突確率は約 10⁻²⁰ と実質ゼロです。
利点: 実装が単純、プライバシー懸念なし。
欠点: 時刻順序がないため、データベースのクラスタインデックスに使うと挿入位置が散らばり、Bツリーのページ分割が頻発してパフォーマンス低下を招く(特に MySQL InnoDB、SQL Server など)。
v7 — Unix時刻プレフィックス + 乱数
v7 は RFC 9562 で新たに標準化された、データベース用途に最適化されたUUIDです。
unix_ts_ms : 48bit ←Unix時刻(ミリ秒)
version : 4bit (= 7)
rand_a : 12bit ←乱数
variant : 2bit (= 10)
rand_b : 62bit ←乱数
上位 48 ビットが Unix 時刻のため、辞書順=時刻順になります。これにより:
- Bツリーインデックスへの挿入が末尾追加になり、ページ分割が起きにくい
- 時間範囲クエリや、直近レコード取得が効率化される
- v4 の乱数の良さ(予測困難性、プライバシー)を 74 ビット分残している
2025年以降の新規プロジェクトでは v7 を第一選択にすることが多くの実装者から推奨されています。PostgreSQL 18 には uuidv7() 関数がネイティブで追加予定(現時点では執筆時点の情報で、正式機能化の状況は要確認)。
v6 と v8 は何者か
v6: v1 の時刻ビットを並べ替えて「辞書順 = 時刻順」にした版。v1 互換性を保ちつつソート性を得たい場合に使う中間形。新規採用する理由は少なく、v7 が登場した今ではv6 より v7 を推奨されるケースがほとんどです。
v8: カスタム用途のためのプレースホルダ。RFC 9562 では「実装が自由にビット割り当てを決めてよい」としていますが、version と variant ビットは守る必要があります。特殊な要件(例: テナントIDを埋め込みたい)がある場合の拡張ポイントとして使用します。
NIL UUID と Max UUID
RFC 9562 は 2 つの特殊 UUID を定義します。
- NIL UUID: 全ビット 0 (
00000000-0000-0000-0000-000000000000) — "未設定"のセンチネル値 - Max UUID: 全ビット 1 (
ffffffff-ffff-ffff-ffff-ffffffffffff) — RFC 9562 で新規追加。範囲の上限として使える
これらは version/variant ビットの規則を破るため、厳密には"invalid"な UUID ですが、仕様が特例として認めています。ID未設定状態の初期値や、range クエリの境界値として役立ちます。
用途別の選択指針
| 用途 | 推奨 | 理由 |
|---|---|---|
| DB の主キー(MySQL/PostgreSQL/SQL Server) | v7 | クラスタインデックスと相性が良く、時刻範囲クエリが効率的 |
| 公開 API のリソースID | v4 または v7 | 推測困難性を保ちつつ、ソート性が要るなら v7 |
| セキュリティトークン | v4 | 時刻が漏れない完全ランダムが安全(厳密には専用の暗号乱数API推奨) |
| 分散システムのログ相関ID | v7 | 時系列で追える + 衝突しない |
| レガシーシステム互換 | v1 / v4 | 既存実装が広く対応しているため |
| 独自のプレフィックス情報を埋め込みたい | v8 | ビット割り当てをカスタマイズ可能 |
実装時の落とし穴
- 文字列保存 vs バイナリ保存: 文字列 36 バイトを CHAR(36) で保存する実装が多いですが、バイナリ 16 バイト(BINARY(16) / UUID型)のほうが 2〜3 倍コンパクトでインデックス効率も上がります。PostgreSQL の
uuid型、MySQL 8.0 のUUID_TO_BIN()を検討してください。 - 時刻取得の精度: v7 のミリ秒精度では、同一ミリ秒内に複数発行すると 12+62=74 ビットの乱数部に頼ることになります。極端な高頻度発行(毎ミリ秒 10万件以上)ではライブラリ実装の衝突対策を確認してください。
- v4 の乱数品質:
Math.random()は暗号論的に安全ではありません。ブラウザならcrypto.randomUUID()、Node.js ならcrypto.randomUUID()、Python ならuuid.uuid4()を使いましょう。
まとめ
- RFC 9562(2024)が現行仕様。旧 RFC 4122 は廃止
- version は13番目のニブルで即判別できる
- v1: MAC+時刻、プライバシーリスク / v4: 完全ランダム、インデックス非効率
- v7: Unix時刻プレフィックス+乱数。2025年以降のDB主キー推奨
- v6 は v1 互換の中間形、v8 はカスタム拡張
- NIL UUID(全0) / Max UUID(全1) は境界値として有用
- 暗号乱数APIを使い、可能ならバイナリで保存
参考文献・ソース
本記事は NanToo(ナンツー)運営の株式会社ヨネマスが編集・公開しています。 ツールの開発現場で得た知見をもとに、実務で役立つ内容を発信しています。