データベースの主キーやAPIリソースのIDとして「UUID」を使うことは現代の開発では当たり前になっています。/users/550e8400-e29b-41d4-a716-446655440000 のようなURLや、JWTの中身を見たことがある方は多いはずです。

しかし「v4とv7の違いは?」「GUIDとは別物?」「データベースの主キーに使う際の注意点は?」といった疑問を持ちながらとりあえず使っている方も多いのではないでしょうか。本記事ではUUIDの仕組みから各バージョンの違い、データベース活用のベストプラクティスまでを実践的に解説します。

UUIDとは?

UUID(Universally Unique Identifier、汎用一意識別子)は、中央管理機関なしにコンピューターシステム内の情報を一意に識別するための128ビットの識別子です。RFC 4122(および後継のRFC 9562)で定義されています。

UUIDは32桁の16進数を8-4-4-4-12のパターンでハイフン区切りした形式で表されます:

550e8400-e29b-41d4-a716-446655440000
^^^^^^^^ ^^^^ ^^^^ ^^^^ ^^^^^^^^^^^^
   8      4    4    4       12

3番目のグループの最初の16進数がバージョン(上の例では 4)を、4番目のグループの最初の桁がバリアントを示します。

UUIDとGUIDは同じもの?

はい、同じです。GUID(Globally Unique Identifier)はMicrosoftがUUID標準を実装した際に使った呼び名です。RFC 4122に基づく仕様は完全に共通で互換性があります。唯一の違いは外見上のもの——Windowsツールでは大文字+波括弧({550E8400-E29B-41D4-A716-446655440000})で表示されることがあります。.NETやSQL ServerがGUIDを求める場面でも標準のUUIDがそのまま使えます。

UUIDのバージョン比較(v1・v3・v4・v5・v7)

UUID標準には複数のバージョンがあり、それぞれ異なる生成戦略を持ちます。

バージョン1 — タイムスタンプ+MACアドレス

v1は60ビットのタイムスタンプ(1582年10月からの100ナノ秒単位)と生成マシンのMACアドレスを組み合わせます。時間と空間を超えてユニークですが、ホストマシンのMACアドレスと生成日時が埋め込まれるためプライバシー上の懸念があり、ユーザー向けシステムでは現在ほとんど使われません。

バージョン3 — 名前ベース(MD5)

v3は名前空間UUIDと名前文字列からMD5ハッシュを使って決定論的にUUIDを生成します。同じ入力から常に同じUUIDが生成されます。MD5は暗号論的に弱いとされるため、新規実装ではv5が推奨されます。

バージョン4 — ランダム(最も一般的)

v4は122ビットの暗号論的乱数を使用します。マシン情報もタイムスタンプも含まれないためプライバシーに安全で、最も広くサポートされているバージョンです。JavaScriptの crypto.randomUUID() が生成するのもこのv4です。

衝突確率は約1/5.3×10³⁶。1秒に10億個生成し続けても衝突確率が50%になるまでに85年かかります。実用上はゼロとみなして問題ありません。

バージョン5 — 名前ベース(SHA-1)

v3と同じ決定論的生成ですが、SHA-1を使用します。v3より安全で、新規実装ではv5が推奨されます。同じユーザーのメールアドレスや商品SKUから常に同じIDを生成したい場合に使います。出力が入力から予測可能なためセキュリティトークンには不向きです。

バージョン7 — 時刻順ランダム(新世代)

UUID v7(RFC 9562、2024年批准)は上位ビットにUnixミリ秒タイムスタンプを埋め込み、残りをランダムデータで埋めます。結果としてグローバルユニークかつ時系列順に並ぶUUIDが生成されます。これはv4のB-treeインデックス問題(後述)を解決します。ORMやデータベースのネイティブサポートが急速に広がっています。

バージョン基盤決定論的時系列順推奨用途
v1時刻+MAC×非推奨(MACアドレス漏洩)
v3MD5ハッシュ×レガシー。新規はv5を使用
v4ランダム××一般用途のデフォルト
v5SHA-1ハッシュ×名前から一意IDを決定論的に生成
v7時刻+ランダム×DB主キー(新規プロジェクト推奨)

各言語でのUUID生成方法

JavaScript / Node.js

// 組み込みのWeb Crypto API(Node.js 14.17+・全モダンブラウザ)
const id = crypto.randomUUID();
// → '550e8400-e29b-41d4-a716-446655440000'

Python

import uuid

# v4(ランダム)
id_v4 = uuid.uuid4()     # UUIDオブジェクト
str(id_v4)                # '550e8400-...'

# v5(名前ベース)
namespace = uuid.NAMESPACE_URL
id_v5 = uuid.uuid5(namespace, 'https://example.com')

PHP

// ramsey/uuidパッケージ(composer require ramsey/uuid)
use Ramsey\Uuid\Uuid;
$id = Uuid::uuid4()->toString();

PostgreSQL

-- 拡張機能を有効化(データベースごとに1回)
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- v4 UUIDを生成
SELECT gen_random_uuid();

-- カラムのデフォルト値として使う
CREATE TABLE users (
    id    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL
);

MySQL 8+

-- 文字列としてv1 UUIDを生成(デフォルト)
SELECT UUID();

-- BINARY(16)として効率的に格納
INSERT INTO users (id, email)
VALUES (UUID_TO_BIN(UUID(), 1), 'user@example.com');

-- 読み出し
SELECT BIN_TO_UUID(id, 1) AS id FROM users;

データベース主キーとしてのUUID活用

分散システムではUUIDを主キーとして使うことが一般的ですが、重要なトレードオフを理解しておく必要があります。

UUIDを主キーに使うメリット

  • 中央管理不要:複数のアプリケーションサーバーやDBシャードが独立してIDを生成しても衝突しません。
  • 連番IDの情報漏洩を防ぐ:整数の連番IDは総レコード数や登録ペースを外部に露出します。UUIDはそのような情報を一切含みません。
  • クライアントサイドでのID生成が可能:DBインサート前にアプリ側でIDを決定できるため、楽観的UI更新などのパターンが実装しやすくなります。

格納形式のトレードオフ

UUIDは VARCHAR(36) ではなく、MySQLは BINARY(16)、PostgreSQLは uuid型 で格納してください。ハイフン付き文字列形式は36バイト必要ですが、BINARY(16)は16バイトで済みます。1000万行・複数のUUIDカラムがあるテーブルでは、この差は数百MBのストレージとインデックスパフォーマンスの大幅な改善につながります。

-- MySQL: 効率的なUUID格納
CREATE TABLE orders (
    id         BINARY(16)   PRIMARY KEY,
    user_id    BINARY(16)   NOT NULL,
    created_at DATETIME     NOT NULL
);

-- インサート(文字列→バイナリ変換)
INSERT INTO orders (id, user_id, created_at)
VALUES (UUID_TO_BIN(UUID(), 1), UUID_TO_BIN('...', 1), NOW());

-- 読み出し(バイナリ→文字列変換)
SELECT BIN_TO_UUID(id, 1) AS id FROM orders;

インデックス断片化問題とUUID v7

v4 UUIDを主キーにした場合の最大の実用上の問題がこれです:v4は完全にランダムなため、新しい行がB-treeインデックスのランダムな位置に挿入されます。これがページスプリットインデックス断片化を引き起こし、時間とともに読み書きのパフォーマンスが低下します。

解決策は時系列順のUUIDを使うことです:

  • UUID v7:上位ビットにUnixミリ秒タイムスタンプを埋め込み、新しいUUIDが常に前のUUIDより大きな値になります。RFC準拠が必要な新規プロジェクトの推奨選択肢です。
  • ULID(Universally Unique Lexicographically Sortable Identifier):128ビット、URLセーフ、時系列順のUUID代替。RFC 4122非準拠ですが広く使われています。

よくある質問(FAQ)

UUID v4の衝突は実際に起こりますか?
実用上は起こりません。衝突確率は約1/5.3×10³⁶です。1秒に10億個生成し続けて衝突確率が50%になるまでに85年かかります。UUID衝突よりもディスク障害やメモリエラーの方が天文学的に起こりやすいです。
整数(AUTO_INCREMENT)とUUIDはどちらを使うべきですか?
単一DBノードで動く小規模システム、クライアントサイドID生成が不要、最小サイズの主キーが欲しい場合は整数が適しています。分散システム、複数のサービスが独立してIDを生成する必要がある場合、レコード数を外部に露出したくない場合はUUIDが適しています。
URLパラメーターにUUIDをそのまま使えますか?
使えます。UUID v4は16進数とハイフンのみで構成されており、すべてURLセーフです。エンコード不要で /users/550e8400-e29b-41d4-a716-446655440000 のようにそのまま使えます。ユーザーの個人情報や総数も露出しません。
UUID v4はオフラインや非サーバー環境でも生成できますか?
できます。v4の生成はデバイスの暗号論的乱数生成器のみを使うため完全にローカルです。ネットワーク不要・サーバー不要・中央レジストリ不要。オフラインファーストアプリや分散システムに最適です。

まとめ

  • UUID v4(ランダム)は一般用途のデフォルト——プライバシーに安全でほぼ全環境でサポートされている。
  • UUID v7(時系列順)はデータベース主キーの新しい推奨選択肢——B-treeインデックス断片化を防ぐ。
  • UUIDとGUIDは同じもの——GUIDはMicrosoftの呼び名にすぎない。
  • MySQLではBINARY(16)、PostgreSQLではuuid型で格納し、VARCHAR(36)は使わない。
  • JavaScriptは crypto.randomUUID() 一行でOK——ライブラリ不要。
  • v5(名前ベース、SHA-1)は同じ入力から決定論的に同じIDを生成したい場合に使う。

UUIDをすぐに生成したい場合は UUIDジェネレーター をご利用ください。1件から100件の一括生成に対応し、コードやデータベースにそのまま使えます。