INSERT或SELECT策略总是返回一行?

时间:2017-10-05 13:24:37

标签: sql postgresql concurrency upsert sql-returning

使用Postgres 9.6,我已按照https://stackoverflow.com/a/40325406/435563中建议的策略执行INSERTSELECT并返回结果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.

2 个答案:

答案 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)