当前位置: 首页 > 知识库问答 >
问题:

竞争条件的PostgreSQL和ActiveRecord子选择

司寇安宜
2023-03-14

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

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

按比例,多个线程将同时读取并写入number的相同值。将其封装在事务中并不能修复竞争条件,因为SELECT不会锁定表。我不能使用自动递增,因为number不是唯一的,它只在给定特定的bar_id时才是唯一的。我看到3种可能的修复方法:

> < li>

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

使用唯一约束并在失败时重试(恶心!)

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

< code >插入foo (bar_id,number)值(42,(从foo中选择MAX(number) 1,其中bar _ id = 42));

所有这些解决方案看起来好像我要重新实现< code>ActiveRecord::Base#save的大部分内容!有没有更简单的方法?

更新:我以为我用< code>Foo.lock找到了答案(真)。其中(bar_id: 42)。最大值(:number),但它使用了聚合查询中不允许的< code > SELECT FOR update

更新 2:我们的 DBA 刚刚通知我,即使我们可以做 插入 INTO foo(bar_id,数字)值(42,(从 foo 中选择 MAX(数字) 1,其中 bar_id = 42)); 这并不能解决任何问题,因为 SELECT 在与 INSERT 不同的锁中运行

共有1个答案

秋光熙
2023-03-14

您的选项包括:

>

  • 在<code>SERIALIZABLE</code>隔离中运行。由于序列化失败,相互依赖的事务将在提交时中止。你会收到很多错误日志垃圾邮件,你会做很多重试,但它会可靠地工作。

    正如您所提到的,定义一个< code>UNIQUE约束,并在失败时重试。与上述问题相同。

    如果有父对象,则可以选择...FOR 在执行最大查询之前更新父对象。在这种情况下,您将从栏中选择 1,其中 bar_id = $1 进行更新。您正在使用 bar 作为该bar_id的所有 foo的锁。然后,您可以知道继续是安全的,只要执行计数器增量的每个查询都可靠地执行此操作。这可以很好地工作。

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

    使用计数器表。这就是我要做的。要么在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;
    

    然后,在同一事务中,将生成的计数器行用于编号。提交时,bar_id的计数器表行将被解锁,以便下一个查询使用。如果回滚,更改将被丢弃。

    我建议使用计数器方法,为计数器使用专用的边表,而不是在bar中添加列。这对模型更为简洁,意味着您可以在bar中创建更少的更新膨胀,这可以降低对bar的查询速度。

  •  类似资料:
    • 问题内容: 我有几个工作人员,每个工作人员都拥有与PostgreSQL的连接。工人用不同的桌子操纵。 工作人员处理来自系统外部的并行请求。被访问的表之一是用户表。当收到一些信息时,我首先需要确保表中有该用户的记录。如果没有记录,我希望首先创建一个。 我正在使用以下成语: 的代码是: 然后测试是否返回任何行。 的(简化)代码为: 当我的系统处理与 同一 用户有关 的 不同信息的并行流时,我经常会收到

    • 9.1. 竞争条件 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法

    • 9.6. 竞争条件检测 即使我们小心到不能再小心,但在并发程序中犯错还是太容易了。幸运的是,Go的runtime和工具链为我们装备了一个复杂但好用的动态分析工具,竞争检查器(the race detector)。 只要在go build,go run或者go test命令后面加上-race的flag,就会使编译器创建一个你的应用的“修改”版或者一个附带了能够记录所有运行期对共享变量访问工具的tes

    • 问题内容: 如何停止MySQL中的竞争条件?当前的问题是由一个简单的算法引起的: 从表中选择一行 如果不存在,将其插入 然后会得到重复的行,或者如果您通过唯一/主键阻止它,则会出现错误。 现在,通常我认为事务在这里有所帮助,但是由于该行不存在,所以事务实际上并没有帮助(或者我是否错过了什么?)。 LOCK TABLE听起来有些矫kill过正,尤其是如果该表每秒更新多次。 我唯一想到的其他解决方案是

    • 我正在做分类帐模块。在这个过程中,我必须按顺序完成这些任务 从源中选择余额(table 1 row1) 从目标中选择余额(table 1 row2) 用一些逻辑修改余额 更新源的余额(table 1 row1) 更新目标余额(table 1 row2) 提交更改 将事务插入到事件表中。 在多线程环境中,线程在前一个线程更新和提交之前获得余额。在Postgres中,锁被强加给正在被访问的行,直到线程