PostgreSQL 和 ActiveRecord 子选择竞争条件

2024-03-02

我在 ActiveRecord 和 PostgreSQL 中遇到竞争条件,我正在读取一个值,然后递增它并插入一条新记录:

num = Foo.where(bar_id: 42).maximum(:number)
Foo.create!({
  bar_id: 42,
  number: num + 1
}) 

在规模上,多个线程将同时读取然后写入相同的值number。将其包装在事务中并不能解决竞争条件,因为 SELECT 不会锁定表。我不能使用自动增量,因为number不是唯一的,只是在特定条件下才是唯一的bar_id。我看到 3 个可能的修复方法:

  • 显式使用 postgres 锁(行级锁?)

  • 使用唯一约束并在失败时重试(哎呀!)

  • 覆盖保存以使用子选择,即

    INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42));

所有这些解决方案似乎我都会重新实现大部分ActiveRecord::Base#save!有更容易的方法吗?

更新: 我以为我找到了答案Foo.lock(true).where(bar_id: 42).maximum(:number)但这使用SELECT FOR UDPATE聚合查询不允许这样做

更新2: 我们的 DBA 刚刚告诉我,即使我们可以这样做INSERT INTO foo (bar_id, number) VALUES (42, (SELECT MAX(number) + 1 FROM foo WHERE bar_id = 42));这并不能解决任何问题,因为 SELECT 与 INSERT 运行在不同的锁中


您的选择是:

  • Run in SERIALIZABLE隔离。相互依赖的事务将在提交时因序列化失败而中止。您将收到大量错误日志垃圾邮件,并且您将进行大量重试,但它会可靠地工作。

  • 定义一个UNIQUE正如您所指出的,约束并在失败时重试。与上面相同的问题。

  • 如果有父对象,则可以SELECT ... FOR UPDATE在执行之前的父对象max询问。在这种情况下你会SELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE。您正在使用bar作为所有人的锁foo与此相关bar_id。然后您就可以知道继续操作是安全的,只要执行计数器增量的每个查询都能可靠地执行此操作。这可以很好地发挥作用。

    这仍然对每个调用进行聚合查询,这(每个下一个选项)是不必要的,但至少它不会像上面的选项那样向错误日志发送垃圾邮件。

  • 使用柜台。这就是我要做的。要么在bar,或者像这样的边桌bar_foo_counter,使用获取行 ID

    UPDATE bar_foo_counter SET counter = counter + 1
    WHERE bar_id = $1 RETURNING counter
    

    或者如果您的框架无法处理,则使用效率较低的选项RETURNING:

    SELECT counter FROM bar_foo_counter
    WHERE bar_id = $1 FOR UPDATE;
    
    UPDATE bar_foo_counter SET counter = $1;
    

    Then, 在同一笔交易中,使用生成的计数器行number。当您提交时,该计数器表行bar_id被解锁以供下一个查询使用。如果回滚,更改将被丢弃。

我推荐计数器方法,使用专用的边桌作为计数器,而不是添加一列bar。这使模型更清晰,并且意味着您可以减少更新膨胀bar,这会减慢查询速度bar.

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

PostgreSQL 和 ActiveRecord 子选择竞争条件 的相关文章

随机推荐

  • 如何关闭ViteJS的模块热加载功能?

    我有一个服务器端渲染production模式Vite应用 我的问题是 网页通常会重新加载并且控制台将显示 vite connecting 我将此追溯到 vite 代码库的热模块重新加载部分 然而 我不想hmr on for producti
  • ggbiplot - 如何不在图中使用特征向量

    我有一个数据集数据 cell line sva 其暗淡为 313 11875 cc pca lt prcomp data cell line sva center TRUE scale TRUE retx TRUE g lt ggbiplo
  • Spring Boot 和 Maven 战争叠加

    我正在使用 Maven 多模块 其中一场战争取决于另一场战争 Spring Boot Web 应用程序依赖于一个仅提供 html 文件的基本 Web 应用程序 当我运行 Spring Boot 应用程序时 我可以从主 web 应用程序 Sp
  • 在 Scala 中使用 json 对象的 play 框架

    你好 我是 scala play 框架的初学者 我创建了一个简单的注册表单并连接到 mysql 来插入行 效果很好 现在我想在同一页面上显示这些插入的行 而不使用 json 刷新页面 请建议我一个想法如何在同一页面上获取插入的行 提前致谢
  • 需要知道每个字段是否已更改,我应该如何在 Hibernate 中对此进行建模

    所以我有一个包含三个字段的类 使用 hibernate 映射到一个表 Class Widget String field1 String field2 String field3 在应用程序启动时 许多实例这些小部件将从外部文件添加到数据库
  • 检索 SQL Server 中的日期; CURRENT_TIMESTAMP 与 GetDate()

    使用 SQL Server 哪一种是用于日期检索的最快或最佳实践方法 之间有区别吗CURRENT TIMESTAMP and GetDate CURRENT TIMESTAMP是标准的 ANSI SQL 因此理论上 如果您需要移动数据库 它
  • 正则表达式:用括号分割忽略引号内的嵌套括号

    我的程序将 SQL VALUES 多行字符串解析为单行字符串数组 典型的输入字符串如下所示 11 1 Service A nested parentheses en 22 2 Service B nested parentheses en
  • Rails:设计:用户确认注册后发送电子邮件

    我的应用程序正在使用 Devise 并且正确发送确认电子邮件 并在用户单击确认链接后正确确认用户 我还想在用户确认后发送第二封电子邮件 关于如何延迟确认或两步确认有很多建议 但没有关于我正在寻找的内容 我可以找到 Devise Module
  • 如何将 HTML 代码添加到 JSF FacesMessage

    默认情况下 每个 JSFFacesMessage以单行形式呈现 我想添加 HTML 换行符 br 消息本身 以便消息显示整齐 我尝试过如下 message new FacesMessage test br test 然而 它被 JSF 转义
  • 如何在另一个方法中的方法中使用局部变量?

    private void UserYoutubeService var youtubeService new YouTubeService new BaseClientService Initializer HttpClientInitia
  • 如何在 Node.js 中向子进程发送“CTRL+C”?

    I tried to spawn child process vvp https linux die net man 1 vvp https linux die net man 1 vvp At the certain time I nee
  • vTigerCRM 7 - 除非手动触发,否则调度程序不会运行任何 cron 作业

    我正在运行 vTiger 7 0 我注意到在这个月的第一天 我没有创建任何发票 然后我查看了调度程序 注意到 上次扫描开始 和 上次扫描结束 字段显示几天内没有任何 cron 作业被触发 cron 作业计划以 15 分钟的间隔触发一次 但
  • PyAPN 和发送之间睡眠的需要

    我正在使用 PyAPN 向 iOS 设备发送通知 我经常一次发送多组通知 如果任何令牌因任何原因损坏 该过程将停止 因此 我使用增强的设置和以下方法 apns gateway server register response listene
  • 为脚本语言设置 Maven?

    Maven 用于构建和管理任何基于 Java 的项目 但是如果项目使用脚本语言会发生什么呢 maven 现在对 TCL 能做的就是复制文件并将它们放在目标目录中的正确位置 我的问题如下 代码在 TCL 中 gt 需要解释器而不是编译器 它看
  • 如何通过 Android API 关闭所有声音和振动

    我正在构建一个 Android 应用程序 我试图在应用程序启动时禁用设备的所有声音和振动 我是新手 所以我找不到如何做到这一点 任何想法 提前致谢 谢谢 我自己回复以完成答案 AudioManager aManager AudioManag
  • apache prefork/mod_wsgi 产生的进程计数似乎过去的配置[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 在生产环境中运行 nginx 反转回 apache mpm prefork mod wsgi 我看到90apache 子进程 当我期望 40 是最大值
  • Apache http 基本身份验证?

    是否有一些简单的代码可以添加到 htaccess文件或我的虚拟主机文件来强制执行 http 基本身份验证 那这个呢 AuthUserFile my derectory htpasswd Require valid user AuthName
  • HTML CSS DIV 面板

    I want to make something like this 到目前为止我所做的 谁能告诉我如何实现这一目标 谢谢 提前 See jsFiddle http jsfiddle net SanaBukhari DAFW9 代码如下 H
  • 每个 Android 应用程序的网络流量监控

    我想知道我是否可以对每个 Android 应用程序进行网络流量监控 基本上看看哪个应用程序正在接收 发送多少数据 我知道有很多应用程序已经做到了这一点 但我想知道如何做到这一点 嗯 你当然可以 一个非常简单的方法是使用交通统计 http d
  • PostgreSQL 和 ActiveRecord 子选择竞争条件

    我在 ActiveRecord 和 PostgreSQL 中遇到竞争条件 我正在读取一个值 然后递增它并插入一条新记录 num Foo where bar id 42 maximum number Foo create bar id 42