【问题标题】:INSERT or SELECT strategy to always return a row?INSERT 或 SELECT 策略总是返回一行?
【发布时间】:2017-10-05 13:24:36
【问题描述】:

使用 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。如果我使用完整模式将选择连接到其中任何一个上,那么在没有争用的情况下,它们都会按预期返回 i = 1。

【问题讨论】:

  • 请同时发布数据样本,如果我没记错,它总是返回一行,因此您甚至可以从 xmin,xmax 判断它是否已更新或插入
  • 添加数据。我认为它与尝试使用连接池保存此叶子出现两次的复杂数据结构的应用程序有关。所以记录被添加到两个不同的连接中。
  • 随着您的更新,我注意到 false 导致更新不会发生的地方 - 答案会有所不同:)
  • 我参考的答案是关于SELECTINSERT,而不是INSERTUPDATE。在第一行以粗体表示。这个问题也是如此。我建议你更新第一句话。

标签: sql postgresql concurrency upsert sql-returning


【解决方案1】:

你的观察似乎不可能。上述命令应该总是为新插入的行或预先存在的行返回一个 id。并发写入不能解决这个问题,因为现有的冲突行已被锁定。此相关答案中的说明:

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

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

prop_type text not null references prop_type(name),

如果您尝试插入违反约束的行,就会发生这种情况。如果表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

你的观察符合罪行:

如果我将 prop_type = 'jargon' 更改为 prop_type = 'foo' 就可以了!

但你的解释是基于误解:

如果即使给出了 where false 子句,表达式也不会改变任何东西,那么似乎不会使用锁。

这不是 Postgres 的工作方式。锁定是采用任何一种方式(上面链接答案中的解释),Postgres 锁定机制甚至从不考虑新行与旧行的比较。

但是,这真的需要依靠我猜测的值吗?还是有更好的方法来确保您获得锁定?

没有。没有。

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

【讨论】:

  • 感谢您的回复。经您的确认,这似乎不太可能。事实上,jargonprop_type 中,而foo 不在。与此同时,我已经切换到使用单个客户端对整个对象图进行序列化,而不是依赖于池,这意味着错误不会重现,但会尝试查看是否可以返回,因为这看起来很奇怪对我来说。
  • @shaunc:你的意思是反过来? fooprop_type不是 jargon?因为如果foo 不是,这将开始变得真的很奇怪......
  • 这就是我的意思(!)但是,我现在无法检查您提到的另一种可能性-我以某种方式错过了一个错误(尽管除非被捕获,否则它们会被抛出应用程序,并且我的测试应用程序日志没有任何信息;虽然​​我现在无法在 pg_log 中最终找到)。所以我倾向于这个选项,因为我知道很难找到真正的错误。正如我所说,我无法使用当前代码进行重现,但这很奇怪,我确实想回去尝试。如果没有错误,我应该尝试寻找什么证据来[dis/]确认发生了奇怪的事情?
  • 对不起——我的意思是我原来的陈述是正确的。测试夹具有create table prop_type ( name text primary key ); insert into prop_type values ('jargon'), ('code');
  • 在违反 FK 约束的情况下,它如何与 foo 一起工作?不可能的。一定是哪里有什么误会……
【解决方案2】:

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)

【讨论】:

  • 问题的介绍具有误导性,实际上是关于INSERTSELECT,而不是INSERTUPDATE。您可能错过了问题中附加的UNION ALL SELECT ...
  • @ErwinBrandstetter 谢谢 - 确实我回答错了问题 :)
猜你喜欢
  • 2021-03-23
  • 2019-12-05
  • 1970-01-01
  • 2018-12-04
  • 2021-05-15
  • 2018-10-24
  • 2020-09-02
  • 2021-11-24
  • 2015-11-18
相关资源
最近更新 更多