Короткий ответ
SERIAL — «синтаксический сахар», создающий отдельную sequence и DEFAULT на колонку, а GENERATED ... AS IDENTITY (стандарт SQL, PostgreSQL 10+) — более современный и строгий способ, явно привязывающий генерацию значения к колонке; в новом коде предпочтительнее IDENTITY.
Как это работает подробнее
Оба механизма используют один и тот же объект sequence, но SERIAL реализован через DEFAULT nextval('seq_name'), который можно случайно переопределить обычным INSERT с явным значением, тогда как GENERATED ALWAYS AS IDENTITY по умолчанию запрещает указывать значение вручную.
Пример кода
example.sql
-- Старый подход
CREATE TABLE t1 (id SERIAL PRIMARY KEY, name TEXT);
-- Современный подход (PostgreSQL 10+)
CREATE TABLE t2 (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT);
-- После ручной вставки с явным id нужно синхронизировать sequence:
SELECT setval(pg_get_serial_sequence('t1', 'id'), (SELECT MAX(id) FROM t1));Что использовать на практике
- Практика. IDENTITY ближе к стандарту SQL:2003, лучше защищает от случайной перезаписи id и чище выглядит в DDL; SERIAL остаётся широко распространён в legacy-коде и старых примерах.
Подводные камни
- Ошибка. Вручную вставляют id без учёта текущего значения sequence (например, при миграции данных), из-за чего последующий автоинкремент пытается вставить уже занятый id и падает с unique violation — нужно вызвать setval() для синхронизации.