SQLLab
Все статьи

Последовательности (SEQUENCE) в PostgreSQL: SERIAL, BIGSERIAL и nextval

Как работают последовательности в PostgreSQL: SERIAL vs BIGSERIAL vs GENERATED ALWAYS, nextval, setval, сброс, ямы в ID, применение в мультитаблицах.

21 марта 2026 г.·4 мин чтения·

Последовательности (SEQUENCE) — механизм генерации уникальных числовых значений. Каждый первичный ключ типа SERIAL использует последовательность под капотом.

SERIAL: синтаксический сахар

-- Это:
CREATE TABLE orders (id SERIAL PRIMARY KEY);

-- Эквивалентно этому:
CREATE SEQUENCE orders_id_seq;
CREATE TABLE orders (
    id INTEGER NOT NULL DEFAULT nextval('orders_id_seq')
);
ALTER SEQUENCE orders_id_seq OWNED BY orders.id;
ТипДиапазонТип данных
SMALLSERIAL1 до 32,767SMALLINT
SERIAL1 до 2,147,483,647INTEGER
BIGSERIAL1 до 9,223,372,036,854,775,807BIGINT

GENERATED ALWAYS: современный стандарт

-- PostgreSQL 10+: стандартный SQL вместо SERIAL
CREATE TABLE users (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    -- ALWAYS: нельзя вставить ID вручную (только OVERRIDING SYSTEM VALUE)
    -- BY DEFAULT: можно вставить ID вручную

    name TEXT
);

-- Или:
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY

GENERATED ALWAYS AS IDENTITY — современная альтернатива BIGSERIAL. Рекомендуется для новых таблиц.


Создание последовательности вручную

CREATE SEQUENCE invoice_number_seq
    START WITH 1000        -- начало
    INCREMENT BY 1         -- шаг
    MINVALUE 1000          -- минимум
    MAXVALUE 9999999       -- максимум
    CYCLE;                 -- начинать заново после максимума (по умолчанию NO CYCLE)

-- Использование
SELECT nextval('invoice_number_seq');  -- 1000
SELECT nextval('invoice_number_seq');  -- 1001

SELECT currval('invoice_number_seq');  -- текущее значение (в рамках сессии)
SELECT lastval();                      -- последнее значение из любой последовательности

-- Установить текущее значение
SELECT setval('invoice_number_seq', 5000);
SELECT setval('invoice_number_seq', 5000, false);  -- следующий nextval вернёт 5000

Общая последовательность для нескольких таблиц

Полезно когда ID должны быть уникальны глобально:

-- Одна последовательность для нескольких таблиц
CREATE SEQUENCE global_entity_id_seq;

CREATE TABLE articles (
    id BIGINT PRIMARY KEY DEFAULT nextval('global_entity_id_seq'),
    title TEXT
);

CREATE TABLE videos (
    id BIGINT PRIMARY KEY DEFAULT nextval('global_entity_id_seq'),
    title TEXT
);

-- ID уникальны между таблицами: статья ID=1, видео ID=2, статья ID=3...

«Ямы» (gaps) в ID: это нормально

Последовательность не транзакционна — при откате транзакции значение не возвращается:

BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 100);  -- id = 42
ROLLBACK;  -- транзакция отменена, но 42 уже «использован»

INSERT INTO orders (user_id, amount) VALUES (1, 200);  -- id = 43, не 42!

Ямы в ID — нормальное поведение. Никогда не полагайтесь на последовательность ID.

Ямы также возникают:

  • При использовании CACHE (кешировании нескольких значений)
  • При конкурентных INSERT

CACHE: производительность при конкуренции

-- Кешировать 50 значений в памяти (быстрее при высокой нагрузке)
ALTER SEQUENCE orders_id_seq CACHE 50;

-- При каждом новом подключении PostgreSQL резервирует 50 значений
-- После перезапуска зарезервированные, но неиспользованные значения теряются → большие ямы

Получить текущее значение последовательности

-- После INSERT с RETURNING:
INSERT INTO orders (user_id, amount) VALUES (1, 500) RETURNING id;

-- Или через currval (только в той же сессии):
INSERT INTO orders (user_id, amount) VALUES (1, 500);
SELECT currval('orders_id_seq');

-- Через lastval() — последняя использованная последовательность:
SELECT lastval();

Сброс последовательности

-- Сбросить в 1 (например после TRUNCATE)
TRUNCATE TABLE orders RESTART IDENTITY;
-- или:
ALTER SEQUENCE orders_id_seq RESTART WITH 1;

-- Установить на максимальный существующий ID + 1 (после ручной вставки данных)
SELECT setval('orders_id_seq', MAX(id)) FROM orders;
-- Следующий INSERT получит MAX(id) + 1

Просмотр и управление последовательностями

-- Все последовательности в схеме
SELECT sequence_name, data_type, start_value, minimum_value, maximum_value, increment
FROM information_schema.sequences
WHERE sequence_schema = 'public';

-- Текущее состояние последовательности
SELECT * FROM pg_sequence WHERE seqrelid = 'orders_id_seq'::regclass;

-- Информация через системные функции
SELECT * FROM pg_sequences WHERE sequencename = 'orders_id_seq';

Антипаттерны с последовательностями

-- ❌ Использовать ID как порядковый номер
SELECT * FROM orders ORDER BY id;
-- ID не гарантирует порядок вставки из-за конкурентных транзакций

-- ✅ Использовать created_at для хронологии
SELECT * FROM orders ORDER BY created_at;

-- ❌ Предполагать нет ям
SELECT COUNT(*) = MAX(id) AS no_gaps FROM orders;
-- Почти всегда false

-- ❌ Делиться последовательностью между приложениями
-- Используйте UUID для распределённых систем

-- ❌ Зависеть от конкретного значения ID в тестах
-- ID зависит от порядка запуска тестов

Итог

SERIALGENERATED ALWAYSВручную
Стандарт SQLНет (PostgreSQL-специфично)✅ SQL:2003
Ручная вставка IDМожноНельзя (ALWAYS)Можно
Диапазондо 2B (INT)НастраиваетсяНастраивается
РекомендацияУстаревает✅ Новые проектыДля особых случаев

Для новых проектов: BIGINT GENERATED ALWAYS AS IDENTITY. Для совместимости: BIGSERIAL. Для глобальной уникальности: UUID.

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

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

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

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