Когда компьютеры только начали активно соединяться в сети, возникла простая, но очень острая проблема: как гарантировать, что два разных устройства, находящиеся в разных частях света, не сгенерируют один и тот же идентификатор?
Представьте себе: два сервера, один в Токио, другой в Берлине, независимо создают идентификаторы для объектов. Если они вдруг создадут одинаковые ID — данные могут перепутаться, перезаписаться, а система рухнуть.
Так в 1990-х, в эпоху бурного развития распределённых систем и сетевых протоколов, в недрах Microsoft и Open Software Foundation (OSF) родилась идея GUID — Globally Unique Identifier, или глобально уникальный идентификатор.
GUID (а в стандарте ISO — UUID, Universally Unique Identifier) — это 128-битное число, выглядящее, например, вот так:
550e8400-e29b-41d4-a716-446655440000
Это как цифровой отпечаток: он настолько длинный и хаотичный, что вероятность коллизии (совпадения двух идентификаторов) практически нулевая.
Создаётся он с использованием:
- времени,
- случайных чисел,
- MAC-адреса устройства (в старых версиях),
- и даже криптографических хэш-функций.
Почему это было важно? Дело в том, что GUID позволил создавать уникальные идентификаторы без центрального сервера, без координации, без блокировок и задержек. Это стало настоящим спасением для:
- распределённых баз данных,
- сетевых протоколов,
- систем документооборота,
- и, конечно, современных API.
UUID в PostgreSQL
GUID/UUID используется повсеместно — от PostgreSQL до облачных сервисов. Они стали невидимыми строителями современного интернета: без них мы бы не смогли так просто объединять мир.
Фактически это просто очень длинное случайное число из 16 байт, которое записано в 16-тиричном формате. Просто очень длинное целое число.
Этот тип данных идеально подходит, если вам нужно гарантировать уникальность значений на уровне всей системы, без зависимости от централизованного генератора идентификаторов. Это особенно полезно в распределённых системах или когда данные генерируются на разных серверах.
Почему не использовать просто INTEGER?
Казалось бы, зачем нужен UUID, если можно просто использовать автоинкрементирующееся число как идентификатор? Давайте разберёмся:
Глобальная уникальность: Если бы ваша база данных работала на нескольких серверах, гарантировать уникальность INTEGER будет сложно. С UUID эта проблема решается.
Безопасность и скрытие данных: UUID труднее предугадать, чем последовательный номер. Это снижает риск утечки информации через предсказуемые идентификаторы (например, /users/1, /users/2).
Распределённые системы: Если данные генерируются в разных частях системы, автоинкрементирующий INTEGER не подходит без сложной синхронизации.
Ещё одно преимущество UUID — это стандарт, поддерживаемый во многих языках программирования и системах хранения данных.
Преимущества использования UUID
Основные плюсы:
- Уникальность: гарантируется уникальность идентификатора, даже если данные создаются на разных серверах или системах.
- Гибкость: можно использовать для первичных ключей, внешних ключей и других задач.
- Масштабируемость: удобно при работе с распределёнными базами данных.
Однако у UUID есть и свои недостатки:
- Размер:
UUIDзанимает больше места в памяти (16 байт), чемINTEGER(4 байта). - Читаемость: сравнительно сложнее читать и запоминать.
Генерация UUID в PostgreSQL
Встроенная функция gen_random_uuid()
В PostgreSQL с версии 13 появилась встроенная функция gen_random_uuid() для генерации случайных UUID. Эта функция возвращает уникальный идентификатор, который можно использовать как значение для столбца типа UUID.
Пример:
SELECT gen_random_uuid();
Результат:
d17fc23b-22e5-4fcb-bf86-1b4c766d77b7
Убедитесь, что расширение pgcrypto установлено (для PSQL 1-12)
В ранних версиях PostgreSQL (до 13-й версии) функция gen_random_uuid() доступна после установки расширения pgcrypto. Если на работе вас заставляют им пользоваться, то вы можете спасти ситуацию, выполнив команду:
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
Это позволит использовать генерацию случайных UUID.
Использование UUID в качестве внешнего ключа
UUID можно удобно использовать для построения связей между таблицами. Допустим у вас есть таблица пользователей:
| id | name | |
|---|---|---|
| d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | Alice | alice@example.com |
| a1d3e15a-abc1-4b51-a320-2d4c859f7467 | Bob | bob@example.com |
| 3c524998-5c24-4e73-836d-a4c6bb3cafcd | Charlie | charlie@example.com |
Создание таблицы orders
Давайте создадим таблицу orders, где user_id будет служить внешним ключом, ссылающимся на id в таблице users.
| order_id | user_id | order_date |
|---|---|---|
| 1a5b7d9c-b1a2-4f8e-9e7a-0a1111111111 | d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | 2024-10-15 10:00:00 |
| 2b6c8e0d-c2b3-5a9f-af8b-1b2222222222 | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-15 10:05:00 |
| 3c7d9f1e-d3c4-6baf-bc9c-2c3333333333 | 3c524998-5c24-4e73-836d-a4c6bb3cafcd | 2024-10-15 10:10:00 |
| 4d8eaf2f-e4d5-7cb0-cdab-3d4444444444 | d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | 2024-10-15 10:15:00 |
| 5e9fb030-f5e6-8dc1-debc-4e5555555555 | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-15 10:20:00 |
Поле user_id связано с полем id таблицы users, что позволяет создавать связи между пользователями и их заказами.
Выборка данных с JOIN
Посмотрим, как связаны данные в таблицах users и orders:
SELECT
u.id AS user_id,
u.name,
o.order_id,
o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id;
Результат:
| user_id | name | order_id | order_date |
|---|---|---|---|
| d17fc23b-22e5-4fcb-bf86-1b4c766d77b7 | Alice | a1d3e15a-abc1-4b51-a320-2d4c859f7467 | 2024-10-20 12:34:56 |
Основные сценарии использования UUID
Идентификаторы пользователей и заказов: в распределённых системах, где данные могут поступать из разных источников.
Отметки для API: UUID часто используется в REST API для идентификации сущностей.
Глобальная синхронизация данных: например, когда данные собираются с разных серверов.
Типичные ошибки и особенности
Попытка генерировать UUID вручную: лучше использовать встроенные функции, такие как gen_random_uuid(), чтобы избежать ошибок.
Избыточное использование: не применяйте UUID там, где достаточно автоинкрементного INTEGER. Например, в локальных таблицах, которые никогда не будут масштабироваться.
Размер: UUID занимает больше места, что может повлиять на производительность запросов, особенно при индексировании.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ