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