UUID v4 или ULID: что выбрать для ID в базе данных
UUID v4 — стандарт де-факто, но у него есть проблема: случайность бьёт по производительности индексов при большом объёме данных. Разбираем UUID v4, v7 и ULID — когда что использовать.
Генератор UUID / ULID →Краткое сравнение: UUID v4 vs ULID vs UUID v7
| Характеристика | UUID v4 | ULID | UUID v7 |
|---|---|---|---|
| Размер | 128 бит (36 символов) | 128 бит (26 символов) | 128 бит (36 символов) |
| Формат | 8-4-4-4-12 hex | Base32 Crockford | 8-4-4-4-12 hex |
| Пример | 550e8400-e29b-41d4-a716-446655440000 | 01ARZ3NDEKTSV4RRFFQ69G5FAV | 018c6d5e-1234-7abc-8def-012345678901 |
| Временная метка | ❌ Нет | ✅ Первые 48 бит (мс) | ✅ Первые 48 бит (мс) |
| Сортируемость | ❌ Случайный | ✅ Монотонный | ✅ Монотонный |
| RFC стандарт | ✅ RFC 4122 | ❌ Нет RFC | ✅ RFC 9562 (2024) |
| Совместимость | ✅ Везде | ⚠️ Требует конвертации | ✅ Везде (UUID-совместим) |
| Производительность индексов | ❌ Плохая при >1M записей | ✅ Хорошая | ✅ Хорошая |
Почему UUID v4 плох для первичного ключа при большом объёме
UUID v4 генерируется полностью случайно — 122 бита случайных данных. Это значит каждый новый UUID попадает в случайную позицию в B-tree индексе базы данных.
Последствия при большом объёме данных:
- Page splits — вставка в середину индексной страницы провоцирует её разбиение
- Фрагментация индекса — индекс становится разреженным, нужен регулярный VACUUM/REINDEX
- Хуже кэш-попадание — рандомный доступ к индексным страницам = больше промахов кэша
- Размер индекса — фрагментированный индекс занимает больше места
Как работает ULID
ULID (Universally Unique Lexicographically Sortable Identifier) состоит из двух частей:
- 48 бит временной метки — миллисекунды от Unix epoch, хватит до 10889 года
- 80 бит случайности — 1.21 × 10²⁴ возможных значений в одну миллисекунду
- Base32 Crockford кодировка — нет проблем с похожими символами (0/O, 1/I/L)
- Монотонность — несколько ULID в одной миллисекунде гарантированно упорядочены
UUID v7 — современный стандарт (RFC 9562, 2024)
UUID v7 — официальный RFC-стандарт, принятый в 2024 году. По структуре похож на ULID, но сохраняет UUID-формат:
Преимущества UUID v7 перед ULID:
- ✅ Официальный RFC стандарт — лучше документирован и поддержан
- ✅ Полная совместимость с полями UUID в PostgreSQL, MySQL, SQLite
- ✅ Работает во всех местах где раньше использовался UUID v4 — без изменений схемы
- ✅ Нативная поддержка в PostgreSQL 17+ (
gen_random_uuid()возможно обновят)
Поддержка в базах данных
PostgreSQL
MySQL / MariaDB
SQLite
Рекомендации по выбору
| Ситуация | Рекомендация | Почему |
|---|---|---|
| Новый проект, PostgreSQL | UUID v7 или ULID | Лучшая производительность индексов с первого дня |
| Существующая система с UUID v4 | Оставить UUID v4 | Миграция рискованна, выгода оправдана только при реальных проблемах с производительностью |
| Микросервисы, несколько БД | UUID v7 | Стандарт RFC, совместим со всеми системами |
| API — ID в URL | ULID | 26 символов без дефисов, URL-безопасный |
| Нужна временная метка в ID | ULID или UUID v7 | Первые биты = timestamp, можно извлечь время создания |
| Публичный API, максимальная совместимость | UUID v4 | Все клиенты понимают UUID, нет путаницы |
Вероятность коллизий
Все три формата на практике свободны от коллизий:
- UUID v4: 2¹²² уникальных значений. Для 50% вероятности коллизии нужно сгенерировать ~2,71 × 10¹⁸ ID.
- ULID: 2⁸⁰ случайных бит в одну миллисекунду = 1,21 × 10²⁴ вариантов. Более того, ULID гарантирует монотонность внутри одной мс — последующий ULID всегда больше предыдущего.
- UUID v7: аналогично ULID по структуре — 74 бита случайности + упорядоченность по времени.
Часто задаваемые вопросы
Что лучше для первичного ключа в PostgreSQL: UUID или ULID?
Для PostgreSQL ULID (или UUID v7) заметно лучше чем UUID v4 в качестве первичного ключа при больших объёмах. UUID v4 полностью случайный — из-за этого B-tree индексы фрагментируются при вставке. ULID и UUID v7 монотонно возрастают по времени, новые записи попадают в конец индекса — меньше разбиений страниц, лучше производительность.
Чем UUID v7 отличается от ULID?
UUID v7 — стандарт RFC 9562 (2024), 128 бит, формат совместим с обычными UUID (hex с дефисами). ULID — 128 бит, Base32 кодировка (26 символов), нет официального RFC. UUID v7 предпочтительнее если нужна совместимость с системами ожидающими UUID-формат. ULID лучше читается человеком и компактнее в URL.
Можно ли использовать UUID как текстовое поле в MySQL?
Технически можно, но это плохая практика: UUID в VARCHAR(36) занимает 36 байт против 16 байт в BINARY(16). Разница в размере индекса значительна при миллионах записей. В MySQL 8+ используйте UUID_TO_BIN() / BIN_TO_UUID() для эффективного хранения UUID.