UUIDのソートと順序付け
UUIDの辞書順ソートの挙動、時刻ベースの順序付けをサポートするバージョン、UUID v1がタイムスタンプを含むにもかかわらず時系列でソートされない理由を解説します。
詳細な説明
UUIDのソート挙動はバージョンによって大きく異なります。この理解は、データベースパフォーマンス、ページネーション、ID順序に依存するすべてのシステムにとって重要です。
バージョン別の辞書順ソート:
| バージョン | ソート時に時系列になるか? | 理由 |
|---|---|---|
| v1 | いいえ | タイムスタンプビットが分散している |
| v3, v5 | いいえ | ハッシュベース、時間データなし |
| v4 | いいえ | 完全にランダム |
| v6 | はい | v1タイムスタンプの並び替え版 |
| v7 | はい | タイムスタンプが最上位ビットに配置 |
UUID v7が正しくソートされる理由: UUID v7は48ビットのUnixタイムスタンプを最上位ビット(位置0-47)に配置します。文字列比較は左から右へ行われ、16進数字は数値と同じ順序でソートされるため、後に生成されたUUIDは必ず前に生成されたものよりも後にソートされます:
018d1a2b-... (generated at time T)
018d1a2c-... (generated at time T+1ms)
018d1a2d-... (generated at time T+2ms)
UUID v1が正しくソートされない理由: UUID v1は60ビットのタイムスタンプを非連続の3つのフィールドに分割し、混乱を招く順序で配置しています:
v1 format: time_low(32) - time_mid(16) - ver(4)+time_hi(12) - var+clock - node
タイムスタンプの下位ビット(time_low)が文字列の先頭に来るため、文字列ソートでは最下位の時間部分が最初に比較されます。これは時系列の逆順です。UUID v6はこの問題を修正するために、タイムスタンプをビッグエンディアン順に並び替えて作られました。
データベースのソートパフォーマンス:
UUID v7カラムでは、別途 created_at カラムを必要とせずUUID自体でページネーションが可能です:
-- Cursor-based pagination with UUID v7
SELECT * FROM events
WHERE id > '018d1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3'
ORDER BY id
LIMIT 20;
UUID v4カラムでは、ソートするとランダムな順序になり実用的な意味がありません。意味のある順序付けには別のインデックス付きカラムが必要です:
-- Must use a timestamp column for ordering with UUID v4
SELECT * FROM events
ORDER BY created_at DESC, id
LIMIT 20;
Javaのソートに関する注意点:
Javaの UUID.compareTo() は内部のlong値を符号付きとして扱うため、文字列表現と同じ辞書順ソートにはなりません:
// This does NOT match string sort order!
Collections.sort(uuidList);
// Correct approach: compare as strings
uuidList.sort(Comparator.comparing(UUID::toString));
// Or use unsigned comparison (Java 8+):
uuidList.sort((a, b) -> {
int cmp = Long.compareUnsigned(
a.getMostSignificantBits(), b.getMostSignificantBits());
if (cmp != 0) return cmp;
return Long.compareUnsigned(
a.getLeastSignificantBits(), b.getLeastSignificantBits());
});
バイナリのソート順序: UUIDが BINARY(16) として格納されている場合、バイト比較(符号なし)により正しくソートされ、16進数文字列表現の辞書順と一致します。これはUUID v7のバイナリストレージのもう一つの利点です。
ユースケース
UUIDのソート挙動はREST APIのカーソルベースページネーションに直接影響します。UUID v7の主キーを使用すれば、最後に取得したUUIDをカーソルとして使用でき、テーブルサイズの増加とともに劣化するオフセットベースのページネーションを排除できます。