2017-10-05 43 views
2

里使用Postgres 9.6,我遵循了https://stackoverflow.com/a/40325406/435563建议做一个INSERTSELECT并返回得到的ID策略:INSERT或SELECT策略总是返回一行?

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子句,如果表达式不会改变任何东西,看起来锁也不会被采用。这真的需要依赖于我猜测一个不会排在行内的值吗?还是有更好的方法来确保你获得锁定?

--- UPDATE ---

的总体情况是,应用程序尝试使用连接池(...自动提交)保存向无环图,并使用该查询来获取同时识别重复。 [事实证明,更聪明的是使用一个事务,只是序列化到一个连接。但是,当有争这里的行为是奇数]

外键约束似乎并没有影响到插入 - 如:

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。

+0

请发布的数据样本为好,如果我不是弄错它始终如果它返回一行,因此你甚至可以从XMIN告诉,XMAX已更新或插入 –

+0

已添加数据。我想它与应用程序试图保存一个复杂的数据结构,其中该叶片出现两次,使用连接池相关。所以记录被添加到两个不同的连接中。 – shaunc

+0

与您的更新我注意到哪里更新不会发生错误 - 与该答案将不同:) –

回答

2

你的观察似乎是不可能的。上述命令应该是总是返回一个id,无论是新插入的行还是预存在的行。并发写入不能混淆这一点,因为现有的冲突行被锁定。说明在这个相关的答案:

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

我看到你的表定义一个FK约束:

prop_type text not null references prop_type(name), 

如果您尝试插入一行违反约束条件,这正是发生的情况。如果在表prop_typename = '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 ='术语'更改为prop_type ='foo',它就可以工作!

但是你的解释是基于误解:

这似乎锁如果表达式不会改变任何事情不采取甚至给出了其中假子句。

这不是Postgres的工作原理。锁定是以任何一种方式进行的(上面的链接答案中的解释),Postgres锁定机制甚至不会考虑新的行与旧的行相比如何。

这是否真的需要依赖于我猜测不会在行中的值?还是有更好的方法来确保你获得锁定?

不,

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

+0

感谢您的回复。有了你的确认,它似乎不太可能。事实上,'行话'在'prop_type'中,而'foo'不在。与此同时,我同时使用单个客户端对整个对象图进行序列化,而不是依赖于一个池,这意味着错误不会再现,但会尝试查看我是否可以返回,因为它看起来很奇怪对我来说。 – shaunc

+0

@shaunc:你的意思是相反的? 'prop_type'中的'foo'和* not *'术语?因为如果'foo'不是*,这会开始变得非常奇怪...... –

+0

这就是我的意思(!)但是,现在我不能检查你提到的其他可能性 - 那里是我错过了某种错误(尽管它们在应用程序中被抛出,除非被捕获,并且我的测试应用程序日志没有任何信息;但我现在无法在pg_log中确定地找到它)。所以我倾向于这个选择,因为我知道它很难找到真正的错误。正如我所说,我不能用现行代码重现,但它确实很奇怪,我确实想回去尝试。如果*没有错误,我应该寻找什么样的证据来证实发生了什么奇怪的事情? – shaunc

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) 
+0

这个问题的介绍是误导性的,它实际上是关于'INSERT'或'SELECT',而不是'INSERT'或'UPDATE'。你可能错过了附加的'UNION ALL SELECT ...'在问题中。 –

+0

@ErwinBrandstetter谢谢 - 确实我回答了错误的问题:) –

相关问题