Последовательности (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;
| Тип | Диапазон | Тип данных |
|---|---|---|
SMALLSERIAL | 1 до 32,767 | SMALLINT |
SERIAL | 1 до 2,147,483,647 | INTEGER |
BIGSERIAL | 1 до 9,223,372,036,854,775,807 | BIGINT |
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 зависит от порядка запуска тестов
Итог
| SERIAL | GENERATED ALWAYS | Вручную | |
|---|---|---|---|
| Стандарт SQL | Нет (PostgreSQL-специфично) | ✅ SQL:2003 | — |
| Ручная вставка ID | Можно | Нельзя (ALWAYS) | Можно |
| Диапазон | до 2B (INT) | Настраивается | Настраивается |
| Рекомендация | Устаревает | ✅ Новые проекты | Для особых случаев |
Для новых проектов: BIGINT GENERATED ALWAYS AS IDENTITY. Для совместимости: BIGSERIAL. Для глобальной уникальности: UUID.