Ruby on rails 当没有设置行锁或表锁时,死锁如何发生?(由rails ActiveRecord触摸引起)

Ruby on rails 当没有设置行锁或表锁时,死锁如何发生?(由rails ActiveRecord触摸引起),ruby-on-rails,postgresql,transactions,deadlock,russian-doll-caching,Ruby On Rails,Postgresql,Transactions,Deadlock,Russian Doll Caching,我正在广泛使用Rails4的模板缓存功能。大量嵌套模板和大量模型上的touch:true。总的来说,它已被证明是一个易于推理的综合解决方案 我最近实现了一个特性,其中创建了多个后台作业,这些作业迭代数百个对象,并创建与它们相关的其他对象。创建这些对象时,会触摸同一用户 因此,这两个作业并行运行: 作业A:连续创建数百个对象,每次触摸用户123 作业B:连续创建数百个对象,每次触摸用户123 部署此功能后,我发现有时postgres会检测到死锁并取消两个正在等待对方的查询。错误中显示的查询始终

我正在广泛使用Rails4的模板缓存功能。大量嵌套模板和大量模型上的
touch:true
。总的来说,它已被证明是一个易于推理的综合解决方案

我最近实现了一个特性,其中创建了多个后台作业,这些作业迭代数百个对象,并创建与它们相关的其他对象。创建这些对象时,会触摸同一用户

因此,这两个作业并行运行:

  • 作业A:连续创建数百个对象,每次触摸用户123
  • 作业B:连续创建数百个对象,每次触摸用户123
部署此功能后,我发现有时postgres会检测到死锁并取消两个正在等待对方的查询。错误中显示的查询始终是触摸查询(使用当前时间更新用户123的更新的_)

我很惊讶出现这种死锁,因为行和表都没有被锁定。第二个事务不应该只是等待第一个事务完成吗


更新:这里的bug的最小sql复制:

您的原始日志不完整,因为第一次
更新没有事务ID,所以它们没有包含在原始日志中

现在有了带有虚拟TXID的日志,您可以看到PostgreSQL的行为完全符合设计

相关线路为:

pid: 33627 tid: 0 vtid: 3/28 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.619282' WHERE "users"."id" = 2

pid: 33628 tid: 0 vtid: 4/25 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.627175' WHERE "users"."id" = 1

pid: 33627 tid: 6723 vtid: 3/28 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.628983' WHERE "users"."id" = 1

pid: 33628 tid: 6724 vtid: 4/25 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.632111' WHERE "users"."id" = 2

pid: 33627 tid: 6723 vtid: 3/28 ERROR:  deadlock detected
pid: 33627 tid: 6723 vtid: 3/28 DETAIL:  Process 33627 waits for ShareLock on transaction 6724; blocked by process 33628.
    Process 33628 waits for ShareLock on transaction 6723; blocked by process 33627.
    Process 33627: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.628983' WHERE "users"."id" = 1
    Process 33628: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.632111' WHERE "users"."id" = 2
在这里:

  • 3/28锁id=2用于更新
  • 4/25锁id=1用于更新
  • 3/28尝试锁定id=1进行更新,阻止4/25持有的锁
  • 4/25尝试锁定id=2进行更新,阻止3/28持有的锁
此时,两个事务都无法进行,因此PostgreSQL将中止其中一个事务

为了防止这种情况发生,应用程序必须确保总是以相同的顺序获取锁。在不可能的情况下,它必须尝试在任何给定事务中只处理一个依赖对象,这样就不会出现排序问题。或者,它必须准备好通过捕获异常并重新发出事务来处理死锁


要了解更多信息,请参阅手册中的。

我建议启用带有
log\u line\u前缀的
log\u语句='all'
,该前缀至少包括pid和事务ID。然后查看两个死锁事务的完整历史记录。谢谢!我将尝试在本地复制环境…@CraigRinger好的,请参阅更新的问题,显示日志中的内容(我应该记录更多吗?@CraigRinger好的,我现在提供了更好的日志(以防您在过去11分钟内查看以前的版本),这不是错误;这完全符合设计要求。每个事务都试图获得由另一个事务修改的行的锁。上面的应用程序中记录的SQL似乎不是这种情况,这正是我所关心的。重新阅读你的虚拟TXID更新日志,我可以看到你的应用程序也是如此。现在写细节。当你说“更新锁”——这是否意味着在事务中发生更新后,该行上有一个隐式锁?是的,这是正确的。文档中的锁定章节介绍了IIRC.Hey@Crig Ringer,大约一年后,我遇到了同样的问题(因为我以前序列化这项工作的方法已从代码中删除)。但现在我渴望一个更全面的解决方案。有什么想法吗?
pid: 33627 tid: 0 vtid: 3/28 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.619282' WHERE "users"."id" = 2

pid: 33628 tid: 0 vtid: 4/25 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.627175' WHERE "users"."id" = 1

pid: 33627 tid: 6723 vtid: 3/28 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.628983' WHERE "users"."id" = 1

pid: 33628 tid: 6724 vtid: 4/25 LOG:  statement: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.632111' WHERE "users"."id" = 2

pid: 33627 tid: 6723 vtid: 3/28 ERROR:  deadlock detected
pid: 33627 tid: 6723 vtid: 3/28 DETAIL:  Process 33627 waits for ShareLock on transaction 6724; blocked by process 33628.
    Process 33628 waits for ShareLock on transaction 6723; blocked by process 33627.
    Process 33627: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.628983' WHERE "users"."id" = 1
    Process 33628: UPDATE "users" SET "updated_at" = '2014-03-27 23:58:02.632111' WHERE "users"."id" = 2