PostgreSQL
Sequences e identity columns
Sequences e identity columns para generar identificadores, entender huecos, caché, concurrencia y diferencias frente a serial.
- Última actualización
- Actualizada
- Nivel
- Aplicación
PostgreSQL
Sequences e identity columns para generar identificadores, entender huecos, caché, concurrencia y diferencias frente a serial.
PostgreSQL utiliza objetos sequence para producir números sin obligar a que las transacciones se bloqueen entre sí sobre una fila contador.
sesión A solicita nextval → 101
sesión B solicita nextval → 102
sesión A hace rollback
resultado: 101 no se reutilizaEsa falta de rollback es deliberada. Permite generar valores concurrentemente sin convertir la sequence en un cuello de botella transaccional.
Una identity column declara que una columna obtiene su valor de una sequence administrada como parte de su definición:
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEYVarias sesiones necesitan crear filas sin calcular manualmente un ID libre.
Patrón inseguro:
SELECT max(id) + 1 FROM orders;Dos transacciones pueden obtener el mismo siguiente valor. Además, escanear o bloquear la tabla para cada inserción no escala.
Una sequence ofrece una operación atómica especializada:
SELECT nextval('orders_id_seq');CREATE SEQUENCE invoice_internal_seq
AS bigint
START WITH 1
INCREMENT BY 1
MINVALUE 1
CACHE 20;Una sequence tiene estado propio y puede utilizarse desde varias tablas, aunque compartirla solo conviene cuando existe una intención real.
Inspección:
SELECT * FROM pg_sequences
WHERE schemaname = 'app';Reserva y devuelve un valor:
SELECT nextval('app.orders_id_seq');El avance no se revierte si la transacción falla.
Devuelve el último valor obtenido por esa sesión para una sequence:
SELECT currval('app.orders_id_seq');Falla si la sesión no llamó antes a nextval. No significa “valor global actual”.
Devuelve el último nextval ejecutado por la sesión sobre cualquier sequence. Es más ambiguo y puede romperse si triggers u otras operaciones consumen sequences.
Para obtener el ID insertado, prefiere:
INSERT INTO orders (...) VALUES (...)
RETURNING id;SELECT setval('app.orders_id_seq', 5000, true);El tercer argumento controla si el valor ya se considera utilizado:
true: el siguiente nextval devuelve 5001.false: el siguiente devuelve 5000.Un uso común tras importar IDs explícitos:
SELECT setval(
pg_get_serial_sequence('app.orders', 'id'),
COALESCE((SELECT max(id) FROM app.orders), 1),
(SELECT count(*) > 0 FROM app.orders)
);Debe ejecutarse con cuidado y sin writers concurrentes que puedan consumir valores durante la sincronización.
id bigint GENERATED ALWAYS AS IDENTITYPostgreSQL rechaza un valor explícito ordinario:
INSERT INTO orders(id, ...) VALUES (10, ...);Para una importación deliberada:
INSERT INTO orders(id, ...)
OVERRIDING SYSTEM VALUE
VALUES (10, ...);id bigint GENERATED BY DEFAULT AS IDENTITYPermite valores explícitos y genera uno cuando se omiten.
Es útil en imports o sincronización, pero aumenta el riesgo de desalinear la sequence si se insertan valores altos manualmente.
Esto genera valores, pero no impone unicidad ni NOT NULL por sí solo como identidad lógica completa:
id bigint GENERATED ALWAYS AS IDENTITYAñade la constraint:
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEYTambién puede existir una identity que no sea la clave principal, aunque rara vez aporta valor.
id bigserial PRIMARY KEYserial no es un tipo real. Es shorthand histórico que crea aproximadamente:
columna bigint
+ sequence
+ DEFAULT nextval(...)
+ ownership asociadoSigue funcionando, pero identity ofrece una definición más estándar y operaciones DDL más claras.
Una sequence asociada puede pertenecer a una columna:
ALTER SEQUENCE app.orders_id_seq
OWNED BY app.orders.id;Esto conecta su lifecycle con la columna. Al eliminar la columna, PostgreSQL puede eliminar la sequence dependiente.
No confundir con el owner de permisos del objeto.
Un rol que inserta puede necesitar privilegios sobre la sequence:
GRANT INSERT ON app.orders TO app_role;
GRANT USAGE, SELECT ON SEQUENCE app.orders_id_seq TO app_role;Con identity y configuraciones concretas, los requisitos efectivos deben verificarse con el rol real.
Para objetos futuros:
ALTER DEFAULT PRIVILEGES FOR ROLE migration_role IN SCHEMA app
GRANT USAGE, SELECT ON SEQUENCES TO app_role;Los default privileges dependen del rol que crea los objetos, no solo del schema.
Los valores pueden faltar por:
ON CONFLICT que consume un default antes de detectar conflicto.Una sequence garantiza generación concurrente, no continuidad.
ALTER SEQUENCE app.orders_id_seq CACHE 100;Cada backend puede reservar bloques en memoria. Esto reduce acceso compartido, pero un crash puede perder valores reservados y aumentar huecos.
Un cache alto puede mejorar workloads de inserción intensiva. No debe elegirse si alguien exige continuidad visual, porque esa exigencia ya es incompatible con una sequence ordinaria.
CREATE SEQUENCE tiny_seq MAXVALUE 999 CYCLE;Al alcanzar el máximo, vuelve al mínimo. Es peligroso para identifiers porque reutiliza valores y puede chocar con filas existentes.
Para claves, normalmente utiliza NO CYCLE y un tipo con rango suficiente.
Una identity integer tiene límite aproximado de 2.1 mil millones positivos. Puede parecer enorme, pero eventos, logs o imports pueden consumirlo rápidamente.
Diagnóstico:
SELECT
sequencename,
last_value,
max_value,
max_value - last_value AS remaining
FROM pg_sequences
WHERE schemaname = 'app';Migrar de integer a bigint antes de agotamiento requiere planificar columna, FKs, índices y aplicaciones.
Un ID mayor no garantiza que la transacción haya confirmado después.
Ejemplo:
T1 obtiene 100 y tarda 10 s
T2 obtiene 101 y confirma primero
T1 confirma despuésOrden por ID no equivale necesariamente a orden de commit ni de creación semántica. Usa timestamps y desempates según el contrato.
BEGIN;
SELECT nextval('orders_id_seq'); -- 100
ROLLBACK;
SELECT nextval('orders_id_seq'); -- 101El 100 queda sin fila. Esto no es corrupción.
Una factura puede requerir numeración con reglas regulatorias: prefijos, rangos autorizados, ausencia o justificación de anulaciones.
No reutilices automáticamente la primary key sequence como número fiscal.
Diseño posible:
CREATE TABLE invoice_number_counters (
series text PRIMARY KEY,
next_number bigint NOT NULL
);Asignación protegida:
UPDATE invoice_number_counters
SET next_number = next_number + 1
WHERE series = $1
RETURNING next_number - 1;Esto crea contención deliberada por serie y todavía necesita política para transacciones fallidas, anulaciones y auditoría. Las reglas legales deben revisarse con el contexto correspondiente.
Puedes combinar:
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
public_id uuid NOT NULL DEFAULT gen_random_uuid() UNIQUEEsto añade otro índice; úsalo cuando el contrato público lo justifica.
Un dump lógico puede restaurar datos y después ajustar sequences mediante comandos incluidos por pg_dump.
Si importas manualmente IDs:
No asumir que copiar filas actualiza automáticamente el estado de la sequence.
TRUNCATE app.test_orders RESTART IDENTITY;Reinicia sequences owned por columnas truncadas.
CONTINUE IDENTITY conserva su estado.
En pruebas es útil. En producción puede reutilizar IDs y romper referencias externas o expectativas de auditoría.
Una identity definida en la tabla particionada genera valores según la definición común. Revisa cómo se insertan filas directamente en partitions y cómo se gestionan defaults en la versión desplegada.
La unicidad global en tablas particionadas tiene requisitos sobre la clave de partición.
Replica WAL y estado de sequence como parte del cluster. Tras failover pueden existir huecos o valores cacheados perdidos, pero no debería volver a un estado que cree colisiones dentro de una promoción correcta.
La replicación lógica de tablas no replica automáticamente el estado de las sequences como cambios de filas ordinarios. En migraciones o switchover debes sincronizarlas explícitamente.
Este detalle es crítico para evitar que el nuevo publisher genere IDs ya usados.
Una sequence local en dos writers independientes puede producir colisiones.
Alternativas:
No conviertas una necesidad distribuida en una configuración improvisada de sequences.
Tabla de orders:
CREATE TABLE app.orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
public_id uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE,
business_id bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);Flujo de inserción:
INSERT INTO app.orders (business_id)
VALUES ($1)
RETURNING id, public_id, created_at;El backend no consulta currval, no calcula IDs y no depende de continuidad.
El default puede consumir un valor aunque no se inserte la fila.
La sequence no se adelanta sola y el siguiente valor puede colisionar más adelante.
DROP de la columna puede dejar un objeto huérfano.
Otra request o conexión no comparte el estado; RETURNING es más seguro.
El nuevo writer puede tener sequence atrasada si no se sincronizó.
ALWAYS exige OVERRIDING SYSTEM VALUE; después debe ajustarse la sequence.
Tiene carrera y coste.
Los huecos invalidan la interpretación.
Contradice el diseño concurrente de sequences.
La sesión puede cambiar.
El insert falla en runtime.
Puede generar unique violations.
Produce paginación o auditoría incorrecta.
nextval concurrentemente.ON CONFLICT DO NOTHING.TRUNCATE RESTART IDENTITY en entorno de prueba.RETURNING desde el driver.Elige con datos de volumen y acceso, no por moda.
nextval no se revierte.RETURNING supera a currval para aplicaciones.setval.ALWAYS y BY DEFAULT?RETURNING id es preferible a currval?ALWAYS restringe valores explícitos; BY DEFAULT permite sobrescribir la generación.nextval futuro puede producir un ID ya existente.Transacciones y ACID conecta la generación de filas con unidades de consistencia, rollback y concurrencia.