【问题标题】:Ignoring errors on insert due to concurrent delete in referenced table由于引用表中的并发删除而忽略插入错误
【发布时间】:2018-03-22 17:31:29
【问题描述】:

我正在使用“insert ... select ... where exists ...”将行批量插入到表中,同时忽略 FK 约束无效的行。但是,当有一个并发事务刚刚删除了引用表中的一行时,这不起作用。

考虑以下 psql 会话:

第 1 节:

coudy=# create table a (x int primary key);
CREATE TABLE
coudy=# create table b (x int, foreign key (x) references a);
CREATE TABLE
coudy=# insert into a values (1);
INSERT 0 1
coudy=# begin;
BEGIN
coudy=# delete from a where x = 1;
DELETE 1

第 2 节:

coudy=# begin;
BEGIN
coudy=# insert into b select v.x from (values (1)) v (x) where exists (select 1 from a where a.x = v.x);

这会正确阻止会话 2。但是,在会话 1 中提交后,会话 2 现在意外失败:

ERROR:  insert or update on table "b" violates foreign key constraint "b_x_fkey"
DETAIL:  Key (x)=(1) is not present in table "a".

我希望该行会被过滤掉,因此不会被插入。在我的实际场景中,这是批量插入,所以理想情况下我希望避免重试。

提高隔离级别没有帮助。

【问题讨论】:

    标签: postgresql


    【解决方案1】:

    您可以更改会话 2 以执行以下操作:

    INSERT INTO b
       SELECT x FROM (VALUES (1)) v(x)
       WHERE EXISTS (SELECT 1 FROM a
                     WHERE a.x = v.x
                     FOR SHARE SKIP LOCKED);
    

    内部SELECT 将跳过所有持有排他锁的行,因为它们正在被更新或删除。

    这不会阻塞或出错,但它可能无法插入一些本来可以正常工作的行(假设会话 1 回滚事务)。

    如果您不介意等待锁定并且不想错过这些极端情况,则必须使用SAVEPOINTs 并使用ROLLBACK TO SAVEPOINT 处理错误以避免重试整个事务。

    【讨论】:

    • 再看一遍,锁完全破坏了性能——在我的快速测试中,插入 500 行现在慢了约 10 倍。有什么乐观的方法可以达到同样的结果吗?感觉这应该是可能的,因为我基本上是通过让数据库丢失可能不一致的行来要求数据库少做一些事情。
    • 锁定行将是一些额外的努力,但 10 倍?正如我所说,另一种选择是接受错误并使用保存点进行恢复。也许这样更快。您不必为每一行建立一个保存点,您可以每 100 行设置一个,如果遇到错误,只需重做这些。
    猜你喜欢
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-28
    • 2020-12-07
    • 2013-06-16
    相关资源
    最近更新 更多