INSERT 或 SELECT 策略总是 return 一行?

INSERT or SELECT strategy to always return a row?

使用 Postgres 9.6,我按照 中推荐的策略执行 INSERTSELECT 和 return 结果 id:

with ins as (
  insert into prop (prop_type, norm, hash, symbols)
  values (
    , , , 
  ) 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 = 

然而,有时这return没什么。无论如何,我都希望它连续 return。我怎样才能修复它以确保它总是 return 是一个 ID?

注意,尽管没有 return 一行,但经检查似乎确实存在该行。我认为问题可能与尝试同时通过两个会话添加相同的记录有关。

问题中的table定义为:

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
);

数据:

EDT DETAIL:  parameters:  = 'jargon',  = 'j2',  = 'lXWkZSmoSE0mZ+n4xpWB',  = '[]'

如果我将 prop_type = 'jargon' 更改为 prop_type = 'foo' 就可以了!如果即使给定 where false 子句,表达式也不会改变任何东西,那么似乎不会锁定。不过,这真的需要取决于我猜测一个不在行中的值吗?或者有没有更好的方法来确保您获得锁?

--- 更新 ---

总体情况是应用程序尝试使用连接池(...使用自动提交)保存有向无环图,并在筛选重复项时使用此查询获取 ID。 [事实证明,更聪明的做法是使用事务并仅序列化到一个连接。但是这里有争用时的行为很奇怪。]

外键约束似乎不影响插入——例如:

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

注意一个有效的 fk 208,另一个无效的 -208。如果我使用完整模式将 select 连接到其中任何一个,那么在没有争用的情况下,它们都会按预期 return i = 1。

https://www.postgresql.org/docs/9.5/static/sql-insert.html

ON CONFLICT DO UPDATE guarantees an atomic INSERT or UPDATE outcome; provided there is no independent error, one of those two outcomes is guaranteed, even under high concurrency.

这是关于您在更新的 post 中提到的锁。现在关于返回行的初始问题 - 我首先不小心阅读了它。现在我看到了 where false - 使用此子句并不总是会返回一行。例如:

t=# create table a(i int, e int);
CREATE TABLE
t=# insert into a select 1,1;
INSERT 0 1
t=# create unique index b on a (i);
CREATE INDEX
---now insert on conflict do nothing:
t=# insert into a select 1,1 on conflict do nothing returning *,xmax,xmin;
 i | e | xmax | xmin
---+---+------+------
(0 rows)

INSERT 0 0
-- where false same effect - no rows
t=# insert into a select 1,1 on conflict(i) do update set e=2 where false returning *,xmax,xmin;
 i | e | xmax | xmin
---+---+------+------
(0 rows)
-- now insert without conflict:
t=# insert into a select 2,2 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax;
 i | e | xmax
---+---+------
 2 | 2 |    0
(1 row)
-- now insert with update on conflict:
INSERT 0 1
t=# insert into a select 1,1 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax;
 i | e |   xmax
---+---+-----------
 1 | 2 | 126943767
(1 row)

你的观察似乎不可能。上面的命令应该 always return 一个 id,用于新插入的行或预先存在的行。由于现有的冲突行已被锁定,并发写入不会对此造成干扰。此相关答案中的解释:

除非抛出异常,当然。在这种情况下,您会收到错误消息而不是结果。你检查了吗?你有适当的错误处理吗? (如果您的应用程序以某种方式丢弃错误消息:1)修复它。 2) 数据库日志中有一个带有默认日志记录设置的附加条目。)

我确实在您的 table 定义中看到了 FK 约束:

prop_type text not null references prop_type(name),

如果您尝试插入违反约束的行,就会发生这种情况。如果 table prop_type 中没有包含 name = 'jargon' 的行,这就是你得到的:

ERROR:  insert or update on table "prop" violates foreign key constraint "prop_prop_type_fkey"
DETAIL:  Key (prop_type)=(jargon) is not present in table "prop_type".

演示:

dbfiddle here

您的观察与犯罪相符:

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.

那不是 Postgres 的工作方式。无论哪种方式都采用锁定(上面链接的答案中的解释),并且 Postgres 锁定机制甚至从不考虑新行与旧行的比较。

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?

没有。没有。

如果缺失的 FK 值确实是问题所在,您可以使用 rCTE 在单个语句中添加缺失的(不同的)值。像您演示的那样对于单行插入很简单,但也适用于一次插入多行。相关: