Given (PostgreSQL 15.3):
create table test (
test_id int2,
test_val int4,
primary key (test_id)
);
If these two transactions are run in parallel at read committed
isolation level:
-- Transaction 1
insert into test (select 1, 1 from pg_sleep(2));
-- Transaction 2
select coalesce(
(select test_val from test where test_id = 1),
(select null::int4 from pg_sleep(5)),
(select test_val from test where test_id = 1)
);
The second transaction returns null
as it should because it is a single statement.
However, if the second transaction is replaced with:
create function select_testval
(id int2) returns int4
language sql strict volatile -- stable function will work as previous version
return (select test_val from test where test_id = id);
-- Transaction 2.1
select coalesce(
(select test_val from test where test_id = 1),
(select null::int4 from pg_sleep(5)),
select_testval(1::int2)
);
It returns 1
, acting as a series of two statements each seeing a different snapshot of data.
So, my questions are:
- Is it a proper behavior for a function call to introduce a separate statement looking at a newer snapshot? If it is, where is it documented?
- Apparently, the
stable
function gets inlined in this case thus remaining a single statement. Is it possible for avolatile
function call to get inlined as well under some circumstances thus changing concurrency semantics? If it is, how to control inlining?
Edit 1. Based on the feedback from comments edited the post to pinpoint the actual problem causing confusion. The original questions were rather trivial.
P. S. For context, I discovered this when was trying to find an idiomatic pure-SQL solution to concurrent inserts with unique constraint problem (1, 2), without resorting to plpgsql
.