如何在 PostgreSQL 中使用 RETURNING 和 ON CONFLICT?

2023-11-26

我在 PostgreSQL 9.5 中有以下 UPSERT:

INSERT INTO chats ("user", "contact", "name") 
           VALUES ($1, $2, $3), 
                  ($2, $1, NULL) 
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;

如果没有冲突,它会返回如下内容:

----------
    | id |
----------
  1 | 50 |
----------
  2 | 51 |
----------

但如果存在冲突,它不会返回任何行:

----------
    | id |
----------

我想退回新的id如果没有冲突则返回现有的列id冲突列的列。
这可以做到吗? If so, how?


The 目前接受的答案对于单个冲突目标、很少的冲突、小元组和没有触发器来说似乎没问题。它避免了并发问题1(见下文)用蛮力。简单的解决方案有其吸引力,副作用可能不太重要。

但对于所有其他情况,请执行以下操作:not无需更新相同的行。即使表面上看不出任何区别,各种副作用:

  • 它可能会触发不应该触发的触发器。

  • 它写入锁定“无辜”行,可能会产生并发事务的成本。

  • 它可能会使该行看起来很新,尽管它很旧(事务时间戳)。

  • 最重要的是, with PostgreSQL 的 MVCC 模型 UPDATE为每个目标行写入新的行版本,无论行数据是否更改。这会导致 UPSERT 本身的性能损失,表膨胀,索引膨胀,表上后续操作的性能损失,VACUUM成本。对少数重复项影响较小,但是massive对于大多数骗子来说。

Plus,有时不实用甚至不可能使用ON CONFLICT DO UPDATE. 手册:

For ON CONFLICT DO UPDATE, a conflict_target必须提供。

A single如果涉及多个索引/约束,则不可能出现“冲突目标”。但这里有一个针对多个部分索引的相关解决方案:

  • 基于具有 NULL 值的 UNIQUE 约束的 UPSERT

回到主题,您可以实现(几乎)相同的效果,而无需空更新和副作用。以下一些解决方案也适用于ON CONFLICT DO NOTHING(没有“冲突目标”),抓住all可能出现的冲突——这可能是可取的,也可能是不可取的。

无并发写入负载

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, ins AS (
   INSERT INTO chats (usr, contact, name) 
   SELECT * FROM input_rows
   ON CONFLICT (usr, contact) DO NOTHING
   RETURNING id  --, usr, contact              -- return more columns?
   )
SELECT 'i' AS source                           -- 'i' for 'inserted'
     , id  --, usr, contact                    -- return more columns?
FROM   ins
UNION  ALL
SELECT 's' AS source                           -- 's' for 'selected'
     , c.id  --, usr, contact                  -- return more columns?
FROM   input_rows
JOIN   chats c USING (usr, contact);           -- columns of unique index

The source列是一个可选的附加项,用于演示其工作原理。您实际上可能需要它来区分两种情况之间的差异(相对于空写入的另一个优势)。

决赛JOIN chats之所以有效,是因为从附加的新插入的行数据修改CTE在基础表中尚不可见。 (同一 SQL 语句的所有部分都会看到基础表的相同快照。)

自从VALUES表达式是独立的(不直接附加到INSERT) Postgres 无法从目标列派生数据类型,您可能必须添加显式类型转换。手册:

When VALUES用于INSERT,这些值都是自动的 强制为相应目标列的数据类型。什么时候 它在其他上下文中使用,可能需要指定 正确的数据类型。如果条目都是带引号的文字常量, 强制第一个足以确定所有的假设类型。

查询本身(不包括副作用)可能会更昂贵一些few欺骗,由于 CTE 的开销和额外的SELECT(这应该很便宜,因为根据定义,完美索引就存在 - 唯一约束是通过索引实现的)。

可能(快得多)更快many重复。额外写入的有效成本取决于许多因素。

但是这里有更少的副作用和隐藏成本任何状况之下。总体来说很可能更便宜。

附加序列仍然是高级的,因为填充了默认值before测试冲突。

关于 CTE:

  • SELECT 类型查询是唯一可以嵌套的类型吗?
  • 删除关系划分中重复的 SELECT 语句

具有并发写入负载

假设默认READ COMMITTED事务隔离。有关的:

  • 并发事务导致竞争条件,对插入有唯一约束

防御竞争条件的最佳策略取决于确切的要求、表和 UPSERT 中的行数和大小、并发事务的数量、冲突的可能性、可用资源和其他因素......

并发问题1

如果并发事务已写入您的事务现在尝试 UPSERT 的行,则您的事务必须等待另一事务完成。

如果另一笔交易以ROLLBACK(或任何错误,即自动ROLLBACK),您的交易可以正常进行。可能存在的较小副作用:序列号中的间隙。但没有丢失行。

如果其他事务正常结束(隐式或显式)COMMIT), your INSERT将检测到冲突(UNIQUE索引/约束是绝对的)和DO NOTHING,因此也不返回该行。 (也无法锁定该行,如并发问题2下面,因为它是不可见.) The SELECT从查询开始时看到相同的快照,也无法返回尚不可见的行。

结果集中缺少任何此类行(即使它们存在于基础表中)!

This 可能就这样。特别是如果您没有像示例中那样返回行并且满意地知道该行在那里。如果这还不够好,还有多种解决方法。

您可以检查输出的行数,如果与输入的行数不匹配,则重复该语句。对于罕见的情况可能已经足够了。重点是启动一个新查询(可以在同一事务中),然后它将看到新提交的行。

Or检查缺失的结果行within相同的查询和覆盖那些具有暴力技巧的人在阿莱克斯托尼的回答.

WITH input_rows(usr, contact, name) AS ( ... )  -- see above
, ins AS (
   INSERT INTO chats AS c (usr, contact, name) 
   SELECT * FROM input_rows
   ON     CONFLICT (usr, contact) DO NOTHING
   RETURNING id, usr, contact                   -- we need unique columns for later join
   )
, sel AS (
   SELECT 'i'::"char" AS source                 -- 'i' for 'inserted'
        , id, usr, contact
   FROM   ins
   UNION  ALL
   SELECT 's'::"char" AS source                 -- 's' for 'selected'
        , c.id, usr, contact
   FROM   input_rows
   JOIN   chats c USING (usr, contact)
   )
, ups AS (                                      -- RARE corner case
   INSERT INTO chats AS c (usr, contact, name)  -- another UPSERT, not just UPDATE
   SELECT i.*
   FROM   input_rows i
   LEFT   JOIN sel   s USING (usr, contact)     -- columns of unique index
   WHERE  s.usr IS NULL                         -- missing!
   ON     CONFLICT (usr, contact) DO UPDATE     -- we've asked nicely the 1st time ...
   SET    name = c.name                         -- ... this time we overwrite with old value
   -- SET name = EXCLUDED.name                  -- alternatively overwrite with *new* value
   RETURNING 'u'::"char" AS source              -- 'u' for updated
           , id  --, usr, contact               -- return more columns?
   )
SELECT source, id FROM sel
UNION  ALL
TABLE  ups;

这与上面的查询类似,但我们用 CTE 添加了一个步骤ups,在我们返回之前complete结果集。最后一个 CTE 在大多数情况下不会执行任何操作。仅当返回结果中缺少行时,我们才使用暴力破解。

还需要更多的开销。与预先存在的行的冲突越多,这种方法就越有可能优于简单方法。

一个副作用:第二个 UPSERT 乱序写入行,因此它重新引入了死锁的可能性(见下文),如果三个或更多写入相同行的事务重叠。如果这是一个问题,您需要一个不同的解决方案 - 例如重复上面提到的整个声明。

并发问题2

如果并发事务可以写入受影响行的相关列,并且您必须确保您找到的行在同一事务的稍后阶段仍然存在,那么您可以锁定现有行CTE 便宜ins(否则会解锁):

...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE  -- never executed, but still locks the row
...

并添加一个锁定子句SELECT以及,像FOR UPDATE.

这使得竞争的写操作等待直到事务结束,此时所有锁都被释放。所以要简短。

更多细节和解释:

  • 如何在 RETURNING from INSERT ... ON CONFLICT 中包含排除的行
  • 函数中的 SELECT 或 INSERT 是否容易出现竞争条件?

僵局?

抵御僵局通过插入行一致的顺序. See:

  • 尽管发生冲突但不执行任何操作,多行插入仍会发生死锁

数据类型和转换

现有表作为数据类型的模板...

独立的第一行数据的显式类型转换VALUES表达可能会不方便。有办法解决这个问题。您可以使用任何现有关系(表、视图...)作为行模板。目标表是用例的明显选择。输入数据会自动强制转换为适当的类型,例如VALUES的条款INSERT:

WITH input_rows AS (
  (SELECT usr, contact, name FROM chats LIMIT 0)  -- only copies column names and types
   UNION ALL
   VALUES
      ('foo1', 'bar1', 'bob1')  -- no type casts here
    , ('foo2', 'bar2', 'bob2')
   )
   ...

这对于某些数据类型不起作用。看:

  • 更新多行时强制转换 NULL 类型

...和名字

这也适用于all数据类型。

在插入表的所有(前导)列时,您可以省略列名称。假设表chats示例中仅包含 UPSERT 中使用的 3 列:

WITH input_rows AS (
   SELECT * FROM (
      VALUES
      ((NULL::chats).*)         -- copies whole row definition
      ('foo1', 'bar1', 'bob1')  -- no type casts needed
    , ('foo2', 'bar2', 'bob2')
      ) sub
   OFFSET 1
   )
   ...

旁白:不要使用保留字 like "user"作为标识符。那是一把上了膛的步枪。使用合法的、小写的、不带引号的标识符。我把它替换为usr.

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

如何在 PostgreSQL 中使用 RETURNING 和 ON CONFLICT? 的相关文章

随机推荐

  • 是否可以在 BigQuery 中使用 MD5 进行哈希处理?

    BigQuery 是否具有 MD5 功能 我知道它有 cityhash 但我特别需要 MD5 谢谢 例如 由于这会出现在 Google 搜索 BigQuery MD5 中 因此值得指出的是 BigQuery 本身支持以下哈希函数标准SQL
  • 相对较慢的python numpy 3D傅里叶变换

    对于我的工作 我需要对大图像执行离散傅立叶变换 DFT 在当前示例中 我需要 1921 x 512 x 512 图像的 3D FT 以及 512 x 512 图像的 2D FFT 现在 我正在使用 numpy 包和相关函数np fft ff
  • Java 中的按位与、按位或或问题

    我的项目中有几行代码 我看不到它们的价值 buffer i currentByte 0x7F currentByte 0x80 它从文件中读取文件缓冲区 存储为字节 然后传输到 buffer i 如图所示 但我无法理解总体目的是什么 有什么
  • 如何使用 CSS 设置警报框样式?

    更新 我读了很多关于这个主题的文章 尝试了一些脚本 需要帮助来找出你能做什么或不能做什么 社区回答了这一切 以下是一个很好的起点 这里的答案摘自下面的社区 谢谢 您无法覆盖默认的警报方式 它是由您的客户端生成的 edge chrome fi
  • 如何在 VB.NET 中获取 Caps Lock 的当前状态?

    如何使用 VB NET 查明 Caps Lock 是否已激活 这是我的后续先前的问题 Control IsKeyLocked Keys 方法 MSDN Imports System Imports System Windows Forms
  • XmlSerializer、“指定”后缀和 IReflect

    我发现如果一个可序列化的领域 财产有一个对应的类型字段Boolean名字为领域 财产带有 指定 后缀的名称 XmlSerializer 有条件地排除该名称领域 财产从序列化过程来看 好的 所以 我想避免这些字段的定义 并在运行时动态添加它们
  • curl 响应显示“不支持 HTTP 版本”,错误 505

    我使用curl发出请求 响应说不支持HTTP版本 错误505 使HTTP版本受支持的步骤是什么 谷歌搜索后 我应该使用curl http2 0 来使其工作 但我的curl 版本不支持该选项 因为它是在curl 7 33 中添加的 而我使用的
  • 我可以从脚本或命令行将文件复制到网上邻居吗? [关闭]

    Closed 这个问题是无关 目前不接受答案 在 Windows XP 中 是否可以通过命令行 批处理文件或者更好的 PowerShell 脚本将文件复制到网上邻居 让我走上这条研究道路的是尝试将文件从用户的计算机发布到 WSS 3 0 文
  • Notepad++ 正则表达式 -> newLine

    我使用 Notepad 我需要删除以 abc 开头的所有行 注意 我不需要将以 abc 开头的行替换为空行 但我需要完全删除这些行 我该如何继续 我想使用正则表达式 尝试更换 abc r n with nothing The 表示一行的开始
  • jQuery 动画回到原始位置

    我正在开发一个网站 其中有一些绝对定位的 div 我需要在单击时调整其大小 然后这些将填充 div 所在的容器 问题是如何让它们切换到去返回到原始位置 顶部 左侧 每个位置都不同 work item toggle toggle functi
  • 单例或类方法[重复]

    这个问题在这里已经有答案了 阅读完对某个问题的回复后question关于 Objective C 中的单例 似乎每个解决方案都在实例访问器中的线程方面做出了一些权衡 IE synchronized self if sharedInstanc
  • 不在对象上下文中时使用 $this - Laravel 4 PHP 5.4.12

    我试图使用变量 this 访问构造函数上的实例 在所有其他方法中 当我打电话时似乎效果很好 this gt event gt method 但在这个方法上它给我一个错误 不在对象上下文中时使用 this 我刚刚对这个问题进行了研究 我发现的
  • Java TLS 套接字:找不到受信任的证书

    让我快速解释一下我想要做什么 我正在尝试用 java 构建我自己的 Apple 推送通知服务 用于测试目的 该服务的工作得益于 TLS 套接字 我有一个 java 客户端来创建 TLS 套接字以向 APN 发送推送通知 我更改了主机 url
  • OUTER JOIN 结果缺少行,没有 WHERE 子句(已找到解决方法)

    更新在底部 我正在尝试执行自外连接 对于每个记录 返回它以及晚于它发生的所有其他记录 如果它本身是最新记录 则返回 NULL 这是我的sql代码 SELECT A CR A REGIS STATUSDATE B REGIS STATUSDA
  • 应用程序在 runTransitionForCurrentState 上崩溃,但不知道为什么

    我尝试过寻找这个 但没有运气 所以希望有一些大师可能知道答案 我在 iTunes Connect 中看到大量关于我的应用程序因特定堆栈跟踪而崩溃的报告 但堆栈跟踪没有显示任何有用的信息 0 Crashed main 0 UIKit 0x18
  • Hibernate 验证“无法初始化 javax.el.E​​xpressionFactory”错误

    我正在尝试使用休眠验证 代码可以编译 但是启动时失败并出现错误 Exception in thread main javax validation ValidationException HV000183 Unable to initial
  • Typescript 中的 React-Redux 连接问题

    我正在尝试制作一个传递给react redux的组件connect功能 组件如下 interface ITestProps id number class TestComponent extends React Component
  • 右边框渐变

    我正在尝试制作右边框 ul 元素为线性渐变 我尝试过以下但没有成功 webkit border image webkit linear gradient top FE2EF7 4AC0F2 0 0 5px 我正在使用 Chrome 非常感谢
  • C - “char var[]”和“char *var”之间的区别?

    我期望以下两个向量在 RAM 中具有相同的表示形式 char a var XXX x00 char p var XXX 但奇怪的是 调用类型的库函数f char argument 如果我使用以下方式调用它 则会破坏正在运行的应用程序f p
  • 如何在 PostgreSQL 中使用 RETURNING 和 ON CONFLICT?

    我在 PostgreSQL 9 5 中有以下 UPSERT INSERT INTO chats user contact name VALUES 1 2 3 2 1 NULL ON CONFLICT user contact DO NOTH