Using Postgres 9.6, I have followed the strategy recommended in https://stackoverflow.com/a/40325406/435563 to do an INSERT
or SELECT
and return the resulting id:
with ins as (
insert into prop (prop_type, norm, hash, symbols)
values (
$1, $2, $3, $4
) on conflict (hash) do
update set prop_type = 'jargon' where false
returning id)
select id from ins
union all
select id from prop where hash = $3
However, sometimes this returns nothing. I would have expected it to return a row no matter what. How can I fix it to insure it always returns an id?
NB, despite not returning a row, the row does seem to exist on inspection. I believe the problem may be related to trying to add the same record via two sessions simultaneously.
The table in question is defined as:
create table prop (
id serial primary key,
prop_type text not null references prop_type(name),
norm text not null,
hash text not null unique,
symbols jsonb
);
Data:
EDT DETAIL: parameters: $1 = 'jargon', $2 = 'j2', $3 = 'lXWkZSmoSE0mZ+n4xpWB', $4 = '[]'
If I change prop_type = 'jargon'
to prop_type = 'foo'
it works! It would seem the lock isn't taken if the expression wouldn't change anything even given the where false
clause. Does this really need to depend on my guessing a value that wouldn't be in the row, though? Or is there a better way to ensure you get the lock?
--- UPDATE ---
The overall situation is that the application tried to save a directed acyclic graph using a connection pool (...with autocommit), and was using this query to get id while winnowing out duplications. [Turns out that much smarter is to use a transaction and just serialize to one connection. But the behavior when there is contention here is odd.]
The foreign key constraint doesn't seem to affect the insert -- e.g.:
create table foo(i int unique, prop_id int references prop(id));
insert into foo values (1, 208);
insert into foo values (1, 208)
on conflict (i) do update set prop_id = 208 where false;
--> INSERT 0 0
insert into foo values (1, 208)
on conflict (i) do update set prop_id = -208 where false;
--> INSERT 0 0
Note one with valid fk 208, the other with invalid -208. If I connect a select onto either of these with the full pattern, then in situations without contention, they both return i = 1 as expected.
See Question&Answers more detail:
os