使用Postgres 9.6,我已按照https://stackoverflow.com/a/40325406/435563中建议的策略执行INSERT
或SELECT
并返回结果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
然而,有时候这没有任何回报。无论如何,我都希望它能够返回一排。我如何修复它以确保它总是返回一个id?
注意,尽管没有返回一行,但该行似乎确实存在于检查中。我认为问题可能与尝试同时通过两个会话添加相同的记录有关。
有问题的表定义为:
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: $1 = 'jargon', $2 = 'j2', $3 = 'lXWkZSmoSE0mZ+n4xpWB', $4 = '[]'
如果我将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连接到其中任何一个,那么在没有争用的情况下,它们都按预期返回i = 1.
答案 0 :(得分:2)
你的观察似乎不可能。上面的命令应该 始终 为新插入的行或预先存在的行返回一个id。由于现有的冲突行被锁定,因此并发写入无法解决这个问题。这个相关答案的解释:
除非引发异常,当然。在这种情况下,您会收到错误消息而不是结果。你检查过了吗?你有错误处理吗? (如果您的应用程序以某种方式丢弃错误消息:1)修正。 2)数据库日志中有一个附加条目,带有默认日志记录设置。)
我确实在表定义中看到了FK约束:
prop_type text not null references prop_type(name),
如果您尝试插入违反约束的行,那就是发生了什么。如果表name = 'jargon'
中没有prop_type
行,那就是你得到的:
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
你的观察结果符合犯罪:
如果我将prop_type ='jargon'更改为prop_type ='foo',它就可以了!
但你的解释是基于误解:
如果表达式不会改变任何东西,即使给出了where false子句,也不会采取锁定。
这不是Postgres的工作方式。锁定是采取任何一种方式(上面链接的答案中的解释),Postgres锁定机制甚至从未考虑新行与旧行的比较。
这真的需要依赖于我猜测不会在行中的值吗?或者有更好的方法来确保你获得锁定吗?
没有。没有。
如果确实缺少FK值,则可以在rCTE的单个语句中添加缺失(不同)值。对于您演示的单行插入很简单,但也适用于同时插入多行。相关:
答案 1 :(得分:1)
https://www.postgresql.org/docs/9.5/static/sql-insert.html
ON CONFLICT DO UPDATE保证原子INSERT或UPDATE结果; 如果没有独立错误,那么这两个结果之一就是 即使在高并发性下也能保证。
这是关于您更新后的帖子中的锁定。现在关于返回行的初始问题 - 我先把它看得很清楚。现在我看到了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)