SQLLab
Все статьи

Первичные ключи в PostgreSQL: SERIAL, BIGSERIAL, UUID или IDENTITY?

Первичные ключи в PostgreSQL: SERIAL vs BIGSERIAL vs GENERATED ALWAYS AS IDENTITY vs UUID. Производительность вставки, проблемы переполнения, когда UUID.

30 марта 2026 г.·5 мин чтения·

Выбор типа первичного ключа — одно из первых решений при создании таблицы, и оно влияет на производительность, масштабирование и архитектуру системы. В PostgreSQL есть несколько способов, и каждый имеет свои нюансы.

SERIAL: устаревший, но распространённый

SERIAL — это псевдотип, который разворачивается в INTEGER + последовательность + DEFAULT nextval(...):

CREATE TABLE users (
  user_id SERIAL PRIMARY KEY,
  email   TEXT NOT NULL
);
-- Эквивалентно:
-- CREATE SEQUENCE users_user_id_seq;
-- user_id INTEGER DEFAULT nextval('users_user_id_seq') NOT NULL

Проблема: последовательность создаётся отдельно и не является жёстко привязанной к таблице. При pg_dump со специфическими опциями или при ручных манипуляциях можно легко потерять связь между таблицей и последовательностью.

SERIAL — целое число (4 байта), максимум ~2.1 миллиарда. Для небольших таблиц — норма. Для пользователей высоконагруженного сервиса — риск переполнения.

-- SMALLSERIAL: 1 до 32 767 — только для совсем маленьких справочников
-- SERIAL:      1 до 2 147 483 647
-- BIGSERIAL:   1 до 9 223 372 036 854 775 807

BIGSERIAL: когда нужен большой диапазон

BIGSERIAL — то же самое, что SERIAL, но тип BIGINT (8 байт). Максимальное значение: 9.2 × 10¹⁸.

CREATE TABLE events (
  event_id BIGSERIAL PRIMARY KEY,
  payload  JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Если ваша система генерирует миллионы записей в день — считайте:

  • 1 млн в день × 365 = 365 млн в год.
  • SERIAL (2.1 млрд) хватит на ~5.7 лет.
  • BIGSERIAL хватит на 25 000 лет при том же темпе.

Практическое правило: используйте BIGSERIAL или BIGINT GENERATED ALWAYS AS IDENTITY для любой таблицы, которая может существенно вырасти.

GENERATED AS IDENTITY: современный стандарт SQL

GENERATED AS IDENTITY появился в PostgreSQL 10 и является частью стандарта SQL:2003. Это рекомендуемый способ создания автоинкрементных колонок:

-- GENERATED BY DEFAULT: можно вставить своё значение явно
CREATE TABLE products (
  product_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  name       TEXT NOT NULL
);

-- GENERATED ALWAYS: нельзя вставить своё значение без OVERRIDING SYSTEM VALUE
CREATE TABLE orders (
  order_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  total    NUMERIC(12,2)
);

Отличие от SERIAL:

  1. Последовательность жёстко привязана к столбцу — не существует отдельно.
  2. Чище интегрируется с pg_dump и репликацией.
  3. GENERATED ALWAYS защищает от случайной вставки своего ID.
-- Настройка начального значения и шага
CREATE TABLE invoices (
  invoice_id BIGINT GENERATED ALWAYS AS IDENTITY
    (START WITH 1000 INCREMENT BY 1) PRIMARY KEY,
  amount NUMERIC(12,2)
);

-- Вставка с явным значением (только для GENERATED BY DEFAULT)
INSERT INTO products (product_id, name) VALUES (9999, 'Тестовый товар');

-- Для GENERATED ALWAYS нужен спецсинтаксис
INSERT INTO orders (order_id, total)
OVERRIDING SYSTEM VALUE
VALUES (9999, 100.00);

UUID: глобально уникальные идентификаторы

UUID (Universally Unique Identifier) — 128-битный идентификатор, уникальный без координации между серверами.

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE sessions (
  session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id    BIGINT REFERENCES users(user_id),
  expires_at TIMESTAMPTZ NOT NULL
);

UUID v4 vs UUID v7

UUID v4 полностью случаен. Выглядит как 550e8400-e29b-41d4-a716-446655440000.

UUID v7 (RFC 9562, PostgreSQL 17+) включает временну́ю метку в начале, что делает UUID монотонно возрастающими:

-- PostgreSQL 17+
SELECT gen_random_uuid();           -- UUID v4 (случайный)
SELECT uuidv7();                    -- UUID v7 (монотонный, только PG17+)

Для PostgreSQL < 17 можно использовать расширение pg_uuidv7.

Плюсы UUID

  • Уникальность гарантирована без обращения к БД — ID можно генерировать на клиенте или в распределённой системе.
  • Безопасность: ID не предсказуемы, нельзя угадать следующий (/users/1001 → понятно, что следующий /users/1002).
  • Идеален для публичных API, распределённых систем, микросервисов.

Минусы UUID v4

  • Случайность = фрагментация индекса. При вставке UUID v4 B-tree индекс первичного ключа пишет в случайные места, что приводит к частым page split операциям и большим файлам индекса.
  • Занимает 16 байт против 8 байт у BIGINT — все внешние ключи становятся тяжелее.
  • Нечитаем в логах и при отладке.

UUID v7 решает проблему фрагментации — благодаря временно́й метке новые записи всегда вставляются в «конец» индекса.

-- Бенчмарк: INSERT 1 млн строк
-- BIGINT IDENTITY:  ~3 с, индекс ~40 MB
-- UUID v4:          ~8 с, индекс ~90 MB (из-за фрагментации)
-- UUID v7:          ~3.5 с, индекс ~45 MB

Числа примерные, но порядок соотношений верный.

Составные первичные ключи

Составной PK используется в промежуточных таблицах связей Many-to-Many:

CREATE TABLE order_items (
  order_id   BIGINT REFERENCES orders(order_id) ON DELETE CASCADE,
  product_id BIGINT REFERENCES products(product_id),
  quantity   INT NOT NULL DEFAULT 1,
  PRIMARY KEY (order_id, product_id)  -- составной PK
);

Составной PK автоматически создаёт индекс (order_id, product_id). Для запросов по product_id отдельно нужен дополнительный индекс:

CREATE INDEX idx_order_items_product ON order_items(product_id);

Когда что выбрать

СценарийРекомендация
Внутренняя таблица, небольшой объёмINTEGER GENERATED ALWAYS AS IDENTITY
Любая таблица с ростом > 1 млн строкBIGINT GENERATED ALWAYS AS IDENTITY
Распределённая система / микросервисыUUID v7 (или UUID v4 если нет PG17)
Публичный API (скрыть объём данных)UUID
Промежуточная таблица M:MСоставной PK из FK-столбцов
Legacy-кодBIGSERIAL (но мигрируйте к IDENTITY)

Итог

SERIAL и BIGSERIAL работают, но GENERATED AS IDENTITY — современный стандарт, который стоит использовать в новых проектах. UUID удобен для распределённых систем и публичных API, но помните о фрагментации индекса при использовании v4. UUID v7 сочетает преимущества обоих подходов.

Закрепите понимание проектирования баз данных в нашем тренажёре SQL.

Похожие статьи

Попробуй на практике

Тренажёр с реальными задачами — бесплатно и без регистрации

Открыть тренажёр →