UUIDはどれも128ビットですが、値の並び方はバージョンごとに違います。この差が、MySQLの主キーにしたときの書き込み方へ影響します。
新しく作るシステムなら、UUIDv7をBINARY(16)で保存する構成を第一候補にします。ただし、イベントの発生時刻や厳密な処理順は別の列で管理します。
UUIDv4とUUIDv7の中身
UUIDv4は、固定されたバージョンとバリアントを除く122ビットをランダムに生成します。前後のUUIDに時間的な関係はありません。
UUIDv7は、先頭48ビットにUnix時刻のミリ秒を入れ、残りの74ビットをランダム値やカウンターなどに使います。RFC 9562は、新しい時刻ベースUUIDにはv7を推奨しています。

- UUIDv4: 生成時刻と無関係。値から時刻は分からない
- UUIDv7: おおむね生成時刻順。先頭ビットから生成時刻を読める
- 分散生成: どちらも可能
UUIDv7の時刻精度はミリ秒です。同一ミリ秒内の順序は生成ライブラリの実装に依存し、サーバー間の時計がずれれば実際の発生順と逆転します。UUIDv7はソートしやすいIDですが、厳密な通し番号ではありません。
MySQLで差が出る場所
InnoDBは主キーをクラスタ化インデックスとして使います。ランダムなUUIDv4では挿入先がB-tree全体へ散らばります。UUIDv7なら新しい行が末尾付近へ集まりやすく、ランダム挿入を減らせます。

InnoDBはセカンダリインデックスにも主キー列を格納します。UUIDをCHAR(36)ではなくBINARY(16)で持てば、インデックスを小さくできます。セカンダリインデックスが多いほど差が積み上がります。
実際の性能差はデータ量、バッファプール、並列度、ストレージで変わります。本番相当の負荷で測ってください。
MySQLでの保存例
UUIDv7はアプリケーション側で生成します。MySQL 8.4のUUID()が返すのはUUIDv1です。
CREATE TABLE events (
id BINARY(16) NOT NULL,
event_time DATETIME(6) NOT NULL,
received_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
source_id BIGINT UNSIGNED NOT NULL,
payload JSON NOT NULL,
PRIMARY KEY (id),
KEY idx_events_time_id (event_time, id),
KEY idx_source_time_id (source_id, event_time, id)
) ENGINE=InnoDB;INSERT INTO events (id, event_time, source_id, payload)
VALUES (UUID_TO_BIN(?, 0), ?, ?, ?);
SELECT BIN_TO_UUID(id, 0), event_time, received_at, payload
FROM events
WHERE source_id = ?
AND event_time >= ?
AND event_time < ?
ORDER BY event_time, id;UUID_TO_BIN(uuid, 1)の第2引数は、UUIDv1の時刻部分を並べ替える機能です。UUIDv7は最初から時刻が上位ビットにあるため、0のまま保存します。BIN_TO_UUID側も同じ指定に揃えます。
メリットと注意点
UUIDv4は実装例が多く、生成時刻を値から読めません。一方、MySQLの主キーにすると挿入先が分散します。
UUIDv7は分散環境で生成でき、追記中心のテーブルと相性がよいIDです。ただし生成時刻が外部へ漏れます。これが困るAPIでは、公開用のランダムIDやトークンを別に用意します。
どちらのUUIDも認可の代わりにはなりません。URLにUUIDを使っていても、アクセス権限は別に確認します。
時系列データのケース別設計

ログと操作履歴
UUIDv7を主キーにし、検索にはevent_timeを使います。クライアントから遅れて届くデータがあるため、発生時刻とreceived_atは分けます。
IoTとオフライン端末
端末が観測したobserved_at、サーバーのreceived_at、端末内のdevice_sequenceを持たせます。サーバーで生成したUUIDv7の時刻は受信時刻に近く、観測時刻の代わりにはなりません。
注文、決済、監査ログ
厳密な順序にはAUTO_INCREMENT、採番テーブル、ストリームのオフセットを使います。UUIDv7だけでは、同一ミリ秒内や時計がずれたノード間の順序を保証できません。
バックフィルと複数テナント
過去データを今日取り込むと、UUIDv7の時刻部分は今日になります。履歴は元のevent_timeで並べます。
テナント単位の時間範囲検索には、(tenant_id, event_time, id)のセカンダリインデックスが必要です。UUIDv7を主キーにしただけでは速くなりません。
BIGINTとの組み合わせ
書き込み量が大きく、セカンダリインデックスも多いテーブルでは、内部主キーをBIGINT UNSIGNED AUTO_INCREMENT、公開IDをUUIDv7にする案があります。セカンダリインデックスが保持する主キーを8バイトに抑えられます。
CREATE TABLE events (
seq_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
public_id BINARY(16) NOT NULL,
event_time DATETIME(6) NOT NULL,
payload JSON NOT NULL,
PRIMARY KEY (seq_id),
UNIQUE KEY uq_public_id (public_id),
KEY idx_event_time (event_time, seq_id)
) ENGINE=InnoDB;IDが二種類になるため、APIやJOINでの取り違えには注意が必要です。UUIDv7だけで必要な性能が出るなら、無理に分ける必要はありません。
選び方
追記中心で、複数プロセスがIDを生成する新規テーブルならBINARY(16)のUUIDv7を選びます。既存のUUIDv4で性能に困っていないシステムは、そのままで構いません。
時系列データでは、UUIDv7を行の識別子、event_timeを業務上の時刻として使います。厳密な順序が必要なら連番やオフセットを追加してください。



