A PostgreSQL sequence allocates numbers independently of whether the caller’s transaction eventually commits. That makes ordinary identifier generation practical under concurrency, but it also makes gaps normal. A sequence number describes an allocation; it does not prove that a row exists, that a transaction committed, or that every smaller number has been used.

Atomic allocation, with conditions

Nextval advances a sequence atomically. Concurrent callers using an ordinary noncycling sequence receive distinct allocations as long as nobody resets it into an already used range. This is not a promise of lock-free implementation or immunity from every administrative operation. CYCLE and explicit resets can reuse values, so the table’s PRIMARY KEY or UNIQUE constraint remains responsible for stored-row uniqueness.

A sequence also has finite bounds. A noncycling sequence raises an error when it cannot allocate another value within those bounds; it does not silently grow into an unlimited integer. Choose the underlying type and increment deliberately, and monitor remaining capacity when a heavily used identifier approaches its configured limit. A wider sequence does not widen a narrower destination column automatically.

Identity and serial columns use sequences to provide default values. The sequence is an allocator, while the column’s constraints define which rows are allowed. Explicitly imported identifiers can leave the allocator behind the table’s data. Repairing that mismatch requires accounting for active writers and reserved values, not merely issuing a live setval based on an uncoordinated maximum.

Gaps and order are different properties

Nextval allocations are not reclaimed on transaction rollback. An unused allocation, an aborted insert, or an INSERT that takes an ON CONFLICT branch can therefore leave a gap. Deleting a row also does not wind its sequence backward. Missing numbers alone are not evidence of lost records or a damaged sequence.

CACHE reserves batches for individual sessions. If one session reserves 1 through 10 and another reserves 11 through 20, observed calls can return 1, then 11, then 2. Ending a session can abandon its unused reservations. Caching improves allocation efficiency, but increasing it trades against continuity and globally ordered observation; even without caching, allocation order is not transaction commit order.

Setval changes sequence state without being undone by a caller’s rollback. Other sessions may also still hold previously cached values, so resetting an active allocator is not a safe gap-removal operation. If uninterrupted business numbering is required, design transactional allocation and cancellation semantics explicitly. That may serialize access to a dedicated counter; it does not inherently require locking every row in the business table.

When it breaks

Currval holds a named sequence’s session-local value, normally established by nextval. Calling it before the session has established a value raises an error; setval with is_called true also changes this session value. Another connection’s activity does not initialize it for you. A connection pool can therefore make a nextval-then-currval workflow fail if the calls run on different database sessions.

INSERT RETURNING is usually clearer when the application needs the identifier attached to the row it just inserted. Lastval is broader than currval because it refers to whichever sequence was used most recently in the session, which can be surprising around triggers. Neither function is a general query for the table’s newest committed row.

Publishing an allocated number outside the database before its transaction commits is another boundary to consider. A crash can leave an uncommitted sequence change uncertain after recovery. The sequence documentation therefore requires committing the allocation before relying on it for persistent external purposes; an allocation alone is not a durable business event.

Check it quickly

Save this as sequence.sql and run psql -X -v ON_ERROR_STOP=1 -f sequence.sql in a disposable PostgreSQL 18 database. Keep normal autocommit outside the explicit transaction. All objects are temporary; the deliberate reset is confined to this session’s test sequence.

\set ON_ERROR_STOP on
CREATE TEMP SEQUENCE ids;
CREATE TEMP TABLE allocated (stage text,id bigint);
DO $$ BEGIN
  PERFORM currval('ids');
  RAISE EXCEPTION 'currval was defined before nextval';
EXCEPTION WHEN object_not_in_prerequisite_state THEN
  RAISE NOTICE 'currval is undefined in this session before nextval';
END $$;
INSERT INTO allocated VALUES ('first',nextval('ids')) RETURNING *;
BEGIN;
SELECT nextval('ids') AS allocation_in_aborted_transaction;
ROLLBACK;
INSERT INTO allocated VALUES ('after rollback',nextval('ids')) RETURNING *;
SELECT currval('ids') AS session_value;
DO $$ BEGIN
  IF (SELECT array_agg(id ORDER BY id) FROM allocated) IS DISTINCT FROM ARRAY[1,3]::bigint[] THEN
    RAISE EXCEPTION 'rollback unexpectedly reclaimed a value';
  END IF;
END $$;
CREATE TEMP TABLE unique_rows (id bigint PRIMARY KEY);
INSERT INTO unique_rows SELECT id FROM allocated;
SELECT setval('ids',1,false);
DO $$ BEGIN
  INSERT INTO unique_rows VALUES (nextval('ids'));
  RAISE EXCEPTION 'duplicate identifier accepted';
EXCEPTION WHEN unique_violation THEN
  RAISE NOTICE 'the primary key rejected the reset sequence value';
END $$;
SELECT currval('ids') AS reissued_value;
DROP TABLE unique_rows,allocated;
DROP SEQUENCE ids;

PostgreSQL 18.6 rejected currval before allocation, returned one for the first row and two inside the aborted transaction, then returned three afterward. The stored allocation list was therefore one and three, proving that rollback did not reclaim two. Currval reported three for this session.

The final reset requested that the next call return one again. The sequence did so, and the primary key rejected that duplicate while currval recorded the reissued value. This distinguishes safe ordinary allocation from table integrity under resets. The script removes its temporary objects; it neither renumbers existing application rows nor proposes repairing production gaps.

Sources