Joh*_*hir 3 activerecord ruby-on-rails
我的应用程序广泛使用touch,以便利用 Rails 的模板缓存系统。当批量中的许多不同对象之间创建许多关系时,我的应用程序会执行某种类型的工作。有时,某些工作会导致级联touches 导致死锁。
我可以针对这种情况进行编码,我经常看到这种情况发生,但看到它却揭示了更大的问题,这可能会发生在其他情况下,尽管可能性很小。
要理解这一点,请想象两个人在同一时刻在 Twitter 上互相关注。他们都单击“关注”,导致在他们之间创建关系对象,然后编辑他们的每条记录touch。如果这些接触交织在一起:
每个进程都使用一个数据库事务,因此这会导致死锁。
我是否错了,这可能发生在我奇怪的批处理作业场景之外的正常应用程序操作中?如果我没记错的话,有什么解决办法吗?我可以以某种方式将touches 移到事务之外吗?(无论如何,最后一次写入获胜对于更新updated_at来说是可以的......)
更新 - 数据模型的更多解释
class Follow
belongs_to :follower, touch: true
belongs_to :followee, touch: true
end
@u1 = User.find(1)
@u2 = User.find(2)
# Background Job 1
Follow.create!(follower: @u1, followee: @u2)
# Background Job 2
Follow.create!(follower: @u2, followee: @u1)
Run Code Online (Sandbox Code Playgroud)
不确定是什么导致了死锁,但您可以在处理两条记录时添加悲观锁,这将阻止另一个请求处理它们,直到锁被释放,在ActiveRecord继续之前将等待锁释放。
User.transaction do
@u1, @u2 = User.lock.where(id: [1,2])
# Those two records are now locked, other transaction instances
# can't proceed till this transaction block is exited
Follow.create!(follower: @u1, followee: @u2)
end
# lock is released here
Run Code Online (Sandbox Code Playgroud)
注意:传递id: [2,1]不会按该顺序返回它们,因此您需要处理该情况。
注意 2:太多的锁定可能会影响您的整体应用程序性能,因为用户模型可能是一个频繁使用的模型,但我想这一切都取决于以下情况发生的频率。
after_create
class Follow
belongs_to :follower
belongs_to :followee
after_create :touch_users
def touch_users
# no locking and direct database update
User.where(id: [follower.id, followee.id]).update_all(updated_at: :Time.now)
end
end
Run Code Online (Sandbox Code Playgroud)
然后控制器会执行正常的事务,或者根本不执行,因为你不需要它
Follow.create!(follower: @u1, followee: @u2)
Run Code Online (Sandbox Code Playgroud)
注意: #update_all不会触发 activerecord 回调,并且查询是直接在数据库上完成的,如果您有任何after_update方法,那么您可能希望避免使用此方法。