UUID v4 vs v7 vs ULID: как выбрать правильный идентификатор для вашей базы данных
Вы создаёте новый сервис, и первое решение обманчиво простое: как должен выглядеть ваш первичный ключ? Автоинкрементные целые числа раскрывают количество записей, порядок создания и ломаются в тот момент, когда нужно объединить данные из разных баз. UUID решают эти проблемы — но теперь нужно выбрать, какой именно UUID.
UUID v4 был стандартом по умолчанию более десяти лет. UUID v7, стандартизированный в RFC 9562 в мае 2024 года, обещает лучшую производительность баз данных за счёт упорядочивания по времени. А ULID — альтернатива от сообщества, появившаяся в 2016 году, — предлагает аналогичные преимущества в более компактном формате. У каждого есть реальные компромиссы, влияющие на производительность, конфиденциальность и долгосрочную поддержку.
Это руководство сравнивает все три варианта — с реальными данными бенчмарков, практическими советами по миграции и чёткой схемой принятия решений на 2026 год.
Что такое UUID? (И почему «GUID» — это то же самое)
UUID (Universally Unique Identifier) — это 128-битное значение, спроектированное для уникальности без центрального органа координации. Стандартный формат — 32 шестнадцатеричных символа, разделённых дефисами: 550e8400-e29b-41d4-a716-446655440000.
Если вы работали с .NET или Windows, вы видели их под названием GUID (Globally Unique Identifier). Это одно и то же — GUID — это название Microsoft для стандарта UUID. Битовая структура, алгоритмы генерации и требования к хранению идентичны.
UUID прошёл через несколько версий. Те, что актуальны сегодня:
| Версия | Стратегия | Стандартизация | Ещё актуально? |
|---|---|---|---|
| v1 | Временная метка + MAC-адрес | RFC 4122 (2005) | Почти полностью заменён v7 |
| v4 | Случайный | RFC 4122 (2005) | Да — текущий стандарт по умолчанию |
| v5 | На основе имени (хеш SHA-1) | RFC 4122 (2005) | Нишевые сценарии |
| v7 | Временная метка + случайность | RFC 9562 (2024) | Да — новая рекомендация |
RFC 9562 однозначно указывает направление: «Реализации ДОЛЖНЫ использовать UUIDv7 вместо UUIDv1 и UUIDv6, если это возможно».
UUID v4: случайный стандарт
UUID v4 генерирует идентификаторы из 122 бит криптографически безопасной случайности. Это пространство ключей примерно 5,3 × 10³⁶ возможных значений — вам понадобится генерировать миллиард UUID в секунду на протяжении 86 лет, чтобы достичь 50% вероятности коллизии.
Как это работает: всего 128 бит, из которых 6 бит зарезервированы для маркеров версии (4) и варианта. Остальные 122 бита поступают от CSPRNG, такого как crypto.getRandomValues().
Пример: f47ac10b-58cc-4372-a567-0e02b2c3d479
Простота — его главное преимущество. Не нужна координация, нет утечки временных меток, нет состояния для поддержки. Поддержка v4 встроена в каждый основной язык и базу данных.
Проблема: случайные UUID фрагментируют вашу базу данных
Чистая случайность имеет свою цену. Когда вы вставляете случайные UUID в индекс B-дерева — по умолчанию для PostgreSQL, MySQL и большинства реляционных баз данных — новые строки попадают в произвольные позиции в дереве. Это вызывает:
- Разделение страниц: база данных постоянно разделяет и реорганизует страницы индекса вместо того, чтобы дописывать в конец.
- Усиление записи: бенчмарки EnterpriseDB показывают, что случайные UUID генерируют в 8 раз больше WAL (журнала опережающей записи), чем последовательные альтернативы — 20 ГБ против 2,5 ГБ при одинаковой нагрузке.
- Падение пропускной способности: на наборах данных, превышающих объём RAM, вставка случайных UUID падает до 20–30% пропускной способности последовательных UUID.
- Потеря пространства: PlanetScale сообщает, что случайные UUID снижают утилизацию страниц B+-дерева MySQL до ~50%, по сравнению с 94% при последовательных ключах.
Это не теоретическая проблема. Buildkite зафиксировали снижение WAL на 50% на своей основной базе данных после перехода на упорядоченные по времени UUID. Shopify измерили снижение длительности INSERT на 50% при переходе с UUID v4 на упорядоченные по времени идентификаторы для ключей идемпотентности платёжной системы.
UUID v4 подходит для небольших таблиц, редких записей или любых случаев, когда UUID не используется как первичный ключ. Для высоконагруженных систем налог от фрагментации накапливается со временем.
UUID v7: упорядоченный по времени и дружественный к базам данных
UUID v7 решает проблему фрагментации, помещая временную метку в начало. Старшие 48 бит кодируют временную метку UNIX epoch в миллисекундах, за которыми следуют ~74 бита криптографически безопасной случайности.
Как это работает:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤
| unix_ts_ms (48 bits) |
├─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤
| unix_ts_ms | ver | rand_a (12 bits) |
├─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤
|var| rand_b (62 bits) |
├─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤─┤
Поскольку UUID, сгенерированные в пределах одной миллисекунды, имеют общий префикс временной метки, они группируются вместе в индексах B-дерева. Новые вставки добавляются ближе к концу дерева, а не разбрасываются случайным образом — тот же паттерн доступа, который делает автоинкрементные целые числа быстрыми.
Пример: 019078e5-d2c7-7b3a-8f1e-4a6d5c8b2e91 — первые 12 шестнадцатеричных символов кодируют время создания.
Что изменил RFC 9562
До RFC 9562 упорядоченные по времени UUID существовали только как неформальные предложения. RFC, опубликованный в мае 2024 года, сделал UUID v7 официальным стандартом IETF с определённой битовой структурой. Он каталогизирует 16 различных нестандартных реализаций упорядоченных по времени идентификаторов — включая ULID, Twitter Snowflake и Instagram ShardId — которые послужили мотивацией для его создания.
Практическое значение: авторы библиотек теперь имеют единую спецификацию для реализации, и базы данных добавляют нативную поддержку. PostgreSQL 18 (выпущенный в конце 2025 года) получил встроенную функцию uuidv7(), что устраняет необходимость в расширениях или генерации на уровне приложения.
Нативная функция uuidv7() в PostgreSQL 18
PostgreSQL 18, выпущенный в конце 2025 года, добавляет две новые функции:
-- Генерация упорядоченного по времени UUID v7
SELECT uuidv7();
-- Результат: 019078e5-d2c7-7b3a-8f1e-4a6d5c8b2e91
-- Генерация случайного UUID v4 (псевдоним для gen_random_uuid())
SELECT uuidv4();
Реализация PostgreSQL добавляет 12-битную субмиллисекундную дробную часть временной метки, обеспечивая лучшее монотонное упорядочивание в рамках одного серверного процесса. Это означает, что даже строки, вставленные в пределах одной миллисекунды, сохраняют порядок создания — важно для высоконагруженных таблиц.
Для более ранних версий PostgreSQL можно генерировать UUID v7 на уровне приложения или использовать расширения, такие как pg_idkit.
UUID v4 vs v7: прямое сравнение
| Свойство | UUID v4 | UUID v7 |
|---|---|---|
| Формат | 128 бит, 36 символов hex | 128 бит, 36 символов hex |
| Случайные биты | 122 | ~74 |
| Сортировка по времени создания | Нет | Да (точность до мс) |
| Производительность индекса B-дерева | Низкая при больших объёмах | Отличная |
| Утечка временной метки | Нет | Да (точность до мс) |
| Стандарт RFC | RFC 4122 / RFC 9562 | RFC 9562 (май 2024) |
| Нативная поддержка в БД | Все основные базы данных | PostgreSQL 18+, список растёт |
| Поддержка в библиотеках | Универсальная | Широкая и растущая |
| Размер хранения | 16 байт (бинарный) | 16 байт (бинарный) |
| Совместимость типов столбцов | uuid / BINARY(16) | uuid / BINARY(16) — тот же столбец, полная совместимость |
Ключевой вывод: v4 и v7 совместимы на уровне столбцов. Они используют один и тот же тип данных uuid, одинаковое 16-байтовое бинарное хранение, одинаковое 36-символьное строковое представление. Оба можно хранить в одном столбце, что делает поэтапную миграцию простой.
Попробуйте сами: UUID Generator — мгновенно генерируйте идентификаторы UUID v4 в браузере с пакетной генерацией и копированием в один клик.
UUID v7 vs ULID: нужен ли вам ещё ULID?
ULID (Universally Unique Lexicographically Sortable Identifier) появился в 2016 году для решения той же проблемы, которую теперь решает UUID v7: упорядоченные по времени, глобально уникальные идентификаторы. Вот как они сравниваются:
| Свойство | UUID v7 | ULID |
|---|---|---|
| Кодировка | 36 символов hex с дефисами | 26 символов Crockford Base32 |
| Временная метка | 48-бит мс epoch | 48-бит мс epoch |
| Случайные биты | ~74 | 80 |
| Стандарт | IETF RFC 9562 | Спецификация сообщества (без RFC) |
| Монотонное упорядочивание | Зависит от реализации | Определено спецификацией — инкремент в пределах той же мс |
| Тип в БД | Нативный столбец uuid | Требует CHAR(26) или BINARY(16) |
| Безопасен для URL | Нет (дефисы) | Да (Base32) |
| Временная метка действительна до | 10889 год | 10889 год |
В чём выигрывает UUID v7
-
Стандартизация. UUID v7 имеет RFC от IETF. Каждая база данных, ORM и среда выполнения уже понимает тип UUID. ULID требует пользовательского парсинга в большинстве экосистем.
-
Нативная поддержка в базах данных. ULID не помещаются в нативный столбец
uuidPostgreSQL без преобразования. Приходится либо хранить как строки (теряя место), либо конвертировать в бинарный формат (теряя кодировку Crockford). UUID v7 вставляется напрямую в существующие столбцыuuid. -
Импульс экосистемы. Bytebase прогнозирует, что индустрия «постепенно откажется от самодельных решений и перейдёт на UUIDv7 как первичный ключ для большинства сценариев». Нативная функция
uuidv7()в PostgreSQL 18 ускоряет этот процесс.
Когда ULID всё ещё имеет смысл
-
Компактное представление. 26 символов против 36 — ULID короче в URL и API. Если длина строки важна для вашего сценария, ULID компактнее.
-
Чуть больше случайности. ULID предоставляет 80 случайных бит против ~74 у UUID v7. На практике у обоих астрономически низкая вероятность коллизий, но у ULID случайная компонента чуть больше.
-
Существующая инфраструктура ULID. Если ваша система уже использует ULID и стоимость миграции существенна, нет срочной причины переходить. Оба обеспечивают сравнимую производительность баз данных, поскольку используют один и тот же 48-битный префикс временной метки.
Для новых проектов в 2026 году UUID v7 — более надёжный выбор. Он стандартизирован, нативно поддерживается базами данных и не требует от вашей команды поддержки логики парсинга ULID.
Производительность баз данных: реальные бенчмарки
Разница в производительности между случайными и упорядоченными по времени идентификаторами хорошо задокументирована на реальных продакшен-системах:
PostgreSQL
| Метрика | UUID v4 | UUID v7 / последовательный |
|---|---|---|
| Пропускная способность INSERT (большой набор данных) | 20–30% базового уровня | 100% базового уровня |
| Объём WAL | ~20 ГБ | ~2,5 ГБ |
| Коэффициент попаданий в кеш | 85% | 99% |
Источник: бенчмарк EnterpriseDB. Разрыв увеличивается по мере того, как наборы данных превышают объём доступной RAM.
MySQL / InnoDB
Движок InnoDB в MySQL использует кластеризованные индексы, где первичный ключ определяет физический порядок строк. Случайные UUID особенно затратны:
- Утилизация страниц B+-дерева падает до ~50% (против 94% при последовательных ключах)
- UUID, хранящийся как
CHAR(36), в 9 раз больше 32-битного целого BINARY(16)— рекомендованный формат хранения: в 4 раза больше целого числа, но достаточно компактный для большинства нагрузок
Источник: анализ PlanetScale.
Когда оставить автоинкремент
UUID — не всегда ответ. Автоинкрементные целые числа остаются правильным выбором, когда:
- Вы работаете с одной базой данных без планов шардинга
- Пропускная способность записи важнее глобальной уникальности
- Количество строк не является конфиденциальной информацией
- Вам не нужно генерировать идентификаторы вне базы данных
Для распределённых систем, микросервисов или любой архитектуры, где идентификаторы должны генерироваться на уровне приложения, UUID v7 даёт и производительность последовательных ключей, и гибкость децентрализованной генерации.
Как генерировать UUID v7
JavaScript / TypeScript
// Using the 'uuid' package (v10+)
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7(); // '019078e5-d2c7-7b3a-8f1e-4a6d5c8b2e91'
Python
# Python 3.x with uuid7 package
from uuid_extensions import uuid7
id = str(uuid7()) # '019078e5-d2c7-7b3a-8f1e-4a6d5c8b2e91'
Go
// Using github.com/gofrs/uuid/v5
import "github.com/gofrs/uuid/v5"
id, _ := uuid.NewV7()
fmt.Println(id.String()) // "019078e5-d2c7-7b3a-8f1e-4a6d5c8b2e91"
PostgreSQL 18+
-- Native function, no extension needed
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT uuidv7(),
customer_id uuid NOT NULL,
created_at timestamptz DEFAULT now()
);
Другие языки
Библиотеки UUID v7 доступны для Rust (крейт uuid v1.4+), Java (uuid-creator), C# (UUIDNext) и PHP (symfony/uid).
Миграция с UUID v4 на v7
Уже используете UUID v4 в продакшене? Хорошая новость: миграция не требует единовременного переключения.
v4 и v7 могут сосуществовать
Обе версии используют один и тот же тип столбца uuid и 16-байтовое бинарное представление. Вы можете начать генерировать v7 для новых записей, пока существующие записи с v4 остаются нетронутыми. Запросы, соединения и индексы работают одинаково — базе данных безразлично, какой версии UUID.
Единственное поведенческое отличие: новые записи с v7 будут сортироваться после старых записей с v4 при упорядочивании по первичному ключу, поскольку префикс временной метки v7 размещает их позже в лексикографическом порядке. Если ваше приложение полагается на порядок UUID (чего не должно быть при v4), проверьте это предположение.
Чек-лист миграции
- Обновите генерацию идентификаторов — переключите генерацию UUID на уровне приложения на v7. Для PostgreSQL 18+ измените значение по умолчанию столбца на
uuidv7(). - Обновите ORM — большинство ORM делегируют генерацию UUID приложению или базе данных. Обновите генератор, а не тип столбца.
- Мониторьте производительность индексов — после переключения новые вставки будут последовательными. Со временем ваши индексы естественным образом станут более эффективными по мере того, как записи v7 будут преобладать.
- Не делайте обратную заливку — конвертация существующих значений v4 в v7 не нужна и нарушит ссылки внешних ключей. Пусть v4 и v7 сосуществуют.
Вопрос конфиденциальности: временные метки в ваших идентификаторах
UUID v7 кодирует время создания с точностью до миллисекунды. Любой, кто видит UUID, может извлечь момент его создания. Для внутренних ключей базы данных это редко вызывает опасения. Но учтите последствия для:
- Публичных API — если ваш API раскрывает идентификаторы сущностей, клиенты могут определить, когда были созданы ресурсы. Это может выявить бизнес-паттерны (объём заказов, темпы роста пользователей).
- Токенов сессий и API-ключей — для секретов предпочтительнее UUID v4. Временная метка в v7 не добавляет ценности для токенов и без необходимости раскрывает информацию о времени.
- Соответствие регуляторным требованиям — в зависимости от юрисдикции, временные метки создания, встроенные в идентификаторы, могут считаться персональными данными, если их можно связать с пользователем.
Практический подход: используйте UUID v7 для первичных ключей базы данных (внутренних), UUID v4 для внешне видимых токенов и API-ключей.
Попробуйте сами: UUID Generator — нужна партия UUID для тестирования? Генерируйте до 25 штук за раз, копируйте по отдельности или скачивайте как текстовый файл.
Часто задаваемые вопросы
Могут ли два UUID быть одинаковыми?
Теоретически да, но практически нет. 122 случайных бита UUID v4 дают пространство ключей 5,3 × 10³⁶. Вам нужно генерировать миллиард UUID в секунду на протяжении 86 лет, чтобы достичь 50% вероятности коллизии. У UUID v7 меньше случайных бит (~74), но коллизии в пределах одной миллисекунды по-прежнему астрономически маловероятны.
Хранить UUID как строки или в бинарном формате?
В бинарном. UUID, хранящийся как CHAR(36), занимает 36 байт; как BINARY(16) — 16 байт, менее половины объёма. Нативный тип uuid в PostgreSQL автоматически хранит данные в 16-байтовом бинарном формате. В MySQL используйте BINARY(16) с конвертацией на уровне приложения. PlanetScale отмечает, что CHAR(36) в 9 раз больше 32-битного целого числа, поэтому бинарное хранение критически важно для больших таблиц.
Как UUID v7 соотносится с Twitter Snowflake?
Оба являются упорядоченными по времени распределёнными идентификаторами, но служат разным целям. Snowflake ID — 64-битные (меньше, быстрее для сравнения), но требуют центрального сервиса координации для назначения идентификаторов воркеров. UUID v7 — 128-битный и не требует координации: любой узел может генерировать его самостоятельно. RFC 9562 явно перечисляет Snowflake среди 16 нестандартных реализаций, послуживших мотивацией для создания UUID v7.
Как генерировать UUID v7 в PostgreSQL до версии 18?
До появления нативной поддержки есть несколько вариантов: расширение pg_idkit, генерация на уровне приложения (генерируйте в коде и передавайте в базу данных) или функция PL/pgSQL, которая вручную собирает временную метку и случайные байты. Обновление до PostgreSQL 18 — самый чистый путь, если ваша инфраструктура это позволяет.
Безопасен ли UUID v7 для API-ключей?
Нет — для секретов используйте UUID v4. Временная метка UUID v7 раскрывает, когда был создан ключ, что является избыточной информацией в контексте безопасности. Для API-ключей и токенов сессий нужна максимальная энтропия без встроенных метаданных. 122 бита чистой случайности UUID v4 от CSPRNG подходят для этого, хотя специализированные форматы токенов могут предлагать дополнительные возможности, такие как встроенное время истечения.
Что выбрать? Схема принятия решений
Выбирайте UUID v4, когда:
- Вам нужна максимальная случайность (токены, API-ключи, идентификаторы сессий)
- Время создания должно оставаться конфиденциальным
- Вы добавляете UUID в небольшую таблицу с редкими записями, где фрагментация индекса не имеет значения
Выбирайте UUID v7, когда:
- UUID служит первичным ключом базы данных
- Вам нужна сортировка по времени без дополнительного столбца
created_at - Пропускная способность записи важна при масштабировании
- Вы хотите преимущества последовательных ключей без центрального координатора
Выбирайте ULID, когда:
- Вам нужно более короткое строковое представление (26 символов против 36)
- Ваша система уже работает на инфраструктуре ULID
- Вам нужны URL-безопасные идентификаторы без кодирования
Для большинства новых проектов в 2026 году UUID v7 — рекомендация по умолчанию. Он сочетает децентрализованную генерацию UUID v4 с производительностью последовательных ключей в базах данных, подкреплённый стандартом IETF и растущей нативной поддержкой. Сближение отрасли вокруг v7 уже идёт полным ходом — встроенная функция uuidv7() в PostgreSQL 18 является самым ясным сигналом.
Какой бы вариант вы ни выбрали, вы можете мгновенно генерировать идентификаторы UUID v4 с помощью UUID Generator — или форматировать API-ответы, содержащие UUID, с помощью JSON Formatter.