Postgresql – How to stop PG from wasting ids when doing ON CONFLICT DO NOTHING

postgresqlquery

I have an empty table with one bigserial id column and one unique text column, and do this:

INSERT INTO tab (col) VALUES ($1) ON CONFLICT DO NOTHING; -- $1 = test
INSERT INTO tab (col) VALUES ($1) ON CONFLICT DO NOTHING; -- $1 = test
INSERT INTO tab (col) VALUES ($1) ON CONFLICT DO NOTHING; -- $1 = test2

The table now has two rows:

1, test
3, test2

I expect and want:

1, test
2, test2

Why is the bigserial id column being increased even when "NOTHING" is happening for the second row due to the uniqueness of the text column?

More like "ON CONFLICT DO SOMETHING".

I doubt that this is related to the real error I'm having and which has been eluding me for months, but I need to straighten this out first as it seems like it could be related.

I could not find any mention in the manual on how to make it not increase the id when it doesn't INSERT any row.

Best Answer

This is Working As Designed™.

Firstly, serial data types are not "real" types, they are just syntactic sugar for integer columns populated from sequences.

Then about sequences the manual says so:

To avoid blocking concurrent transactions that obtain numbers from the same sequence, a nextval operation is never rolled back; that is, once a value has been fetched it is considered used and will not be returned again. This is true even if the surrounding transaction later aborts, or if the calling query ends up not using the value. For example an INSERT with an ON CONFLICT clause will compute the to-be-inserted tuple, including doing any required nextval calls, before detecting any conflict that would cause it to follow the ON CONFLICT rule instead. Such cases will leave unused “holes” in the sequence of assigned values.

Subsequently (no pun intended), you cannot "stop PG from wasting ids" (even if you stop using Postgres altogether, because all other DBMSes behave in the same manner).