Postgresql – SERIALIZABLE transaction information leak via serial

concurrencyisolation-levelpostgresqlsequence

I'm trying to implement a deterministic configuration for database sessions, and one problem seems to be with sequences. The following example demonstrates:

CREATE TABLE foo (id serial, n integer);

-- connection 1
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

-- connection 2
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
insert into foo (n) values (1),(2),(3);

-- connection 1
select nextval('fun_id_seq') - (select count(*) from fun) as leak;

leak 
----------
       4 
(1 row)   

This looks like a dirty read. If I was to run the transactions serially I would not get this result. Why is this then considered SERIALIZABLE?

Thanks

Best Answer

For various performance and concurrency reasons, sequences are non-transactional, but atomic. In other words, they always give a distinct value to each session, but they don't reuse values, nor are they "gapless" where they return a continuous sequence of numbers.

The second transaction in your example was handed distinct sequence values for each of the rows inserted, and since that is atomic, but non-transactional, the first transaction sees the sequence's nextval increased even though the transaction isolation level is SERIALIZEABLE.

The behavior is noted at the bottom in the Sequence Manipulation Functions documentation.

Neil Conway also explains this quite well in his post FAQ: Using Sequences in PostgreSQL, including a link to a method of creating a "gapless" sequence that should behave how you had initially expected, but at the cost of some concurrency and performance.