INSERT ON CONFLICT DO NOTHING 和 SELECT 之间的竞争条件

2023-12-26

Does a SELECT查询以下INSERT … ON CONFLICT DO NOTHING给定默认事务隔离(读已提交)的情况下,语句总是找到一行?

我想要INSERT-or-SELECT一个表中的一行,然后在第二个表中插入行时引用该行。自从RETURNING不能很好地配合ON CONFLICT https://stackoverflow.com/q/34708509/1048572,到目前为止我已经使用过一个简单的热膨胀系数 https://stackoverflow.com/q/40323799/1048572即使该行已经存在,也应该始终为我提供标识列值:

$id = query(
  `WITH ins AS (
    INSERT INTO object (scope, name)
    VALUES ($1, $2)
    ON CONFLICT (scope, name) DO NOTHING
    RETURNING id
  )
  SELECT id FROM ins
  UNION  ALL
  SELECT id FROM object WHERE scope = $1 AND name = $2
  LIMIT 1;`,
  [$scope, $name]
)
query(
  `INSERT INTO object_member (object_id, key, value)
  SELECT $1, UNNEST($2::text[]), UNNEST($3::int[]);`
  [$id, $keys, $values]
)

然而,我了解到此 CTE 在并发写入负载下并不完全安全 https://stackoverflow.com/a/15950324/1048572,当不同的事务插入同一行时,upsert 和 select 可能都会出现空的情况。

在答案中(还有here https://dba.stackexchange.com/q/212580/188406)建议使用另一个查询来执行SELECT:

启动一个新命令(在同一事务中),然后该命令可以看到上一个查询中的这些冲突行。

如果我理解正确的话,那就意味着要做

$id = query(
  `INSERT INTO object (scope, name)
  VALUES ($1, $2)
  ON CONFLICT (scope, name) DO NOTHING
  RETURNING id;`,
  [$scope, $name]
)
if not $id:
  $id = query(
    `SELECT id FROM object WHERE scope = $1 AND name = $2;`
    [$scope, $name]
  )
query(
  `INSERT INTO object_member (object_id, key, value)
  SELECT $1, UNNEST($2::text[]), UNNEST($3::int[]);`
  [$id, $keys, $values]
)

甚至缩短为

query(
  `INSERT INTO object (scope, name)
  VALUES ($1, $2)
  ON CONFLICT (scope, name) DO NOTHING;`,
  [$scope, $name]
)
query(
  `INSERT INTO object_member (object_id, key, value)
  SELECT (SELECT id FROM object WHERE scope = $1 AND name = $2), UNNEST($3::text[]), UNNEST($3::int[]);`
  [$scope, $name, $keys, $values]
)

我相信这足以防止特定的竞争条件(在中称为“并发问题 1”)这个答案 https://stackoverflow.com/a/42217872/1048572) - 但我不能 100% 确定没有错过任何东西。

还有“并发问题2”呢?如果我理解正确的话,这是关于另一个事务删除或更新现有行,在INSERT and SELECT语句 - 当使用多个查询而不是 CTE 方法时更有可能发生这种情况。我到底应该如何处理呢?我假设锁定SELECT with FOR KEY SHARE在第二个代码片段中是必要的 - 但我是否也需要在第三个代码片段中使用它id在同一个查询中使用?如果这有助于简化答案,让我们假设object只能插入或删除,但永远不能更新。


为了绝对确保第一个表中的单行存在,并且返回它的 ID,您可以创建一个如下所示的函数:

  • 函数中的 SELECT 或 INSERT 是否容易出现竞争条件? https://stackoverflow.com/a/15950324/939860

确保该行也stays在交易期间,只需确保其已锁定即可。如果你INSERT行,无论如何它都被锁定了。如果你SELECT现有的id,你必须显式锁定它 - 就像你建议的那样。FOR KEY SHARE https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS只要有(非部分、非功能性)就足以满足我们的目的UNIQUE索引于(scope, name),考虑到您的情况,这是安全的假设ON CONFLICT clause.

CREATE OR REPLACE FUNCTION f_object_id(_scope text, _name text, OUT _object_id int)
  LANGUAGE plpgsql AS
$func$
BEGIN
LOOP
   SELECT id FROM object
   WHERE  scope = $1
   AND    name  = $2
   -- lock to prevent deletion in the tiny time frame before the next INSERT
   FOR    KEY SHARE
   INTO   _object_id;

   EXIT WHEN FOUND;

   INSERT INTO object AS o (scope, name)
   VALUES ($1, $2)
   ON     CONFLICT (scope, name) DO NOTHING
   RETURNING o.id
   INTO   _object_id;

   EXIT WHEN FOUND;
END LOOP;
END
$func$;

您实际上只需要在可以想象并发事务可能会发生的情况下锁定该行DELETE它(你不UPDATE)在极短的时间范围内SELECT和下一个INSERT陈述。

另外,如果您有FOREIGN KEY约束来自object_member.object_id to object.id(这似乎是可能的),无论如何,引用完整性是得到保证的。如果您不添加显式锁,并且在中间删除了该行,则会出现外键冲突,并且INSERT to object_member连同整个交易一起被取消。否则,与DELETE必须等到事务完成,然后被相同的 FK 约束取消,因为依赖行现在已经存在(除非它被定义为CASCADE...)因此,通过锁定(或不锁定),您可以决定是否阻止DELETE or the INSERT在这种情况下。

然后你的电话就只剩下:

query(
  `WITH o(id) AS (SELECT f_object_id($1, $2))
   INSERT INTO object_member (object_id, key, value)
   SELECT o.id, UNNEST($3::text[]), UNNEST($4::int[])
   FROM   o;`
  [$scope, $name, $keys, $values]
)

由于您显然将多行插入到object_member, 我搬家了f_object_id($1, $2)到 CTE 以避免重复执行 - 这会work,但毫无意义的昂贵。

在 Postgres 12 或更高版本中我会通过添加来明确这一点MATERIALIZED https://www.postgresql.org/docs/current/sql-select.html#SQL-WITH(自从INSERT隐藏在函数中):

WITH o(id) AS MATERIALIZED (SELECT f_object_id($1, $2)) ...

旁白:对于多个unnest() in the SELECT列表中,请确保您使用的是 Postgres 10 或更高版本。看:

  • SELECT 子句中多个返回集合的函数的预期行为是什么? https://stackoverflow.com/questions/39863505/what-is-the-expected-behaviour-for-multiple-set-returning-functions-in-select-cl/39864815#39864815

细节问题

在同一事务中使用多个查询在应用程序逻辑中执行此操作是否会产生任何影响(除了执行时间之外)?

基本上没有。唯一的区别是性能。嗯,代码短,可靠性高。客观上,每个循环在数据库和客户端之间来回更容易出错。但除非你的交易竞争非常激烈,否则你几乎不会循环。

另一个考虑是:事情很棘手,大多数开发人员不理解。封装在服务器端函数中,它不太可能被下一个应用程序程序员(或您自己)破坏。您还必须确保它也被实际使用。无论哪种方式,正确记录你这样做的原因......

我真的想知道我的第二个片段是否安全,或者为什么不安全(给出关于可见性的引用SELECT之后INSERT).

Mostly安全,但不是绝对。而接下来单独SELECT将看到与先前的 UPSERT 竞争的事务的(现已提交的)行,同时没有什么可以阻止第三个事务再次删除它。该行尚未被锁定,当它不可见时您无法执行此操作,并且 Postgres 中没有可用的通用谓词锁定。

考虑一下(T1、T2、T3 是并发事务):

                               T2: BEGIN transaction
T1: BEGIN transaction
                               T2: INSERT object 666
T1: UPSERT object 666
    unique violation?
    -> wait for T2
                               T2: COMMIT
T1: unique violation -> NO ACTION
    finish statement
    can't return invisible object 666
                                             T3: DELETE object 666 & COMMIT
T1: SELECT object 666 -> no row!
    BOOM!

通常情况下,这种情况发生的可能性极小。
但这是可能的。因此就有了循环。

另一种选择是SERIALIZABLE事务隔离 https://www.postgresql.org/docs/current/transaction-iso.html#XACT-SERIALIZABLE。通常更昂贵,并且您需要为序列化失败做好准备。第 22 条。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

INSERT ON CONFLICT DO NOTHING 和 SELECT 之间的竞争条件 的相关文章

  • postgresql:插入...(选择*...)

    我不确定它是否是标准 SQL INSERT INTO tblA SELECT id time FROM tblB WHERE time gt 1000 我正在寻找的是 如果 tblA 和 tblB 位于不同的数据库服务器中怎么办 Postg
  • 3 个表的 SQL 查询(或联接)

    第一次在 Stack Overflow 上问问题 很棒的资源 但是只有一件事真正让我作为 SQL 新手感到困惑 我有三个表 我想获取与鲍勃的学生相关的所有导师的姓名 表 1 教师 ID Name 1 Bob 表 2 学生 STUDENT I
  • 更好地理解 SQL Server 中的架构

    就像标题一样 我还是一个SQLServer菜鸟 当我创建表 Mytable 时 数据库中显示 dbo Mytable 但有人能让我更好地理解模式吗 另外 在 Server 2008 TSQL 一书中 Itzik 说 在你的数据库中 表属于模
  • SQL Server:为什么 ISO-8601 格式的日期依赖于语言?

    我需要一些帮助来理解 SQL Server 中的日期格式处理 如果您尝试以下操作 它将返回正确的结果 SET LANGUAGE English SELECT CAST 2013 08 15 AS DATETIME 2013 08 15 00
  • 使用 PostGIS 拥有通用 GEOGRAPHY 列

    在 PostgreSQL 9 PostGIS 1 5 中 有没有办法创建 通用 GEOGRAPHY 列 我指的是接受 POINT 以及 POLYGON LINESTRING 等的列 到目前为止 我只在 Internet 上看到过像 GEOG
  • 如何加速spark df.write jdbc到postgres数据库?

    我是 Spark 新手 正在尝试使用 df write 加速将数据帧的内容 可以有 200k 到 2M 行 附加到 postgres 数据库 df write format jdbc options url psql url spark d
  • SQL - 需要查找重复记录但排除反向事务

    我有一张交易表 偶尔会有 重复条目 如果 当管理员发现这些重复条目时 他们将撤销交易 从而创建负值 但由于监管要求 原始重复条目仍然保留 我想创建一个 SQL 查询 并使用 Crystal Reports 来制作报告 以便管理员轻松查找重复
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • 从 call_log 中获取最大并发通话数

    我需要帮助在 MySQL 5 0 77 中编写一个查询 根据下面所示的数据 办公室一天的通话量 返回并发电话呼叫的峰值数量 我只是想知道一天中任何特定时间同时打电话的人数最多是多少 首先 这是 MySQL 表 CREATE TABLE ca
  • postgresql 不同的不工作

    我使用以下代码从数据库获取值 但是当我编写这段代码时 测试看看问题出在哪里 我注意到查询没有从数据库中获取不同的值 这是查询 select distinct ca id as id acc name as accName pIsu name
  • 没有提示指令的直连接中表的顺序是否会影响性能?

    所有基于 SQL 的 RDBMS 10 年前的版本 直接连接查询 没有提示指令 中的表顺序是否会对最佳性能和内存管理产生影响 听说最后一个join应该是最大的表 您的数据库的查询优化器如何处理这种情况 回答你的问题 是的 表的顺序在连接中有
  • 通过 C# SqlCommand 执行合并语句不起作用

    我正在第一次尝试使用临时表和MERGE语句通过更新 SQL 表SqlCommandC 中的对象 我正在开发的程序旨在首先将大量记录 最多 20k 导出到 Excel 电子表格中 然后 用户可以搜索并替换特定值 并根据需要更新任意多记录中的任
  • 解析错误:语法错误,意外的 T_RETURN [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 遇到这个问
  • Postgres - 即使我的角色/用户已被授予“读取”权限,也无法从表中“选择”

    我在 postgres 上有一个管理员角色 用户和一个开发人员角色 开发人员角色继承了我为将来方便而创建的 readaccess 角色的属性 如果我运行 du 我得到 Role name Attributes Member of devel
  • PLpgSQL 函数不返回匹配的标题

    当给定文本时 我试图返回电影名称以及演员和工作人员的数量 当我输入字符串并使用 ilike 时 我的查询返回不匹配的标题 我之前创建了一个视图 其中包含要在函数中输入的电影标题和工作人员数量 我的代码是 create or replace
  • 向带有检查约束 SQL 的表添加列

    我想向表中添加一列 然后添加一个检查约束以确保其大于 0 我似乎无法让它在 oracle sl Developer 中运行 Alter TABLE store101 add column Base salary Number 7 2 con
  • MySQL“列计数与第 1 行的值计数不匹配”是什么意思

    这是我收到的消息 ER WRONG VALUE COUNT ON ROW 列计数与第 1 行的值计数不匹配 这是我的全部代码 我的错误在哪里 DROP TABLE student CREATE TABLE employee emp id I
  • 针对约 225 万行的单表选择查询的优化技术?

    我有一个在 InnoDB 引擎上运行的 MySQL 表 名为squares大约有 2 250 000 行 表结构如下 squares square id int 7 unsigned NOT NULL ref coord lat doubl
  • 多边形内的 SQL 地理点在 STIntersect 上不返回 true(但使用 Geometry 返回 true)

    我不想仅仅为了在 STIntersect 中返回 true 而将地理数据转换为几何图形 下面是 SQL 中的代码 DECLARE point GEOGRAPHY GEOGRAPHY Point 1 1 4326 DECLARE polygo

随机推荐