我的应用程序广泛使用touch
以利用Rails的模板缓存系统。当批量中的许多不同对象之间创建了许多关系时,我的应用程序会执行某种类型的工作。有时,某些工作会导致导致级联touch
导致死锁。
我可以针对我经常看到它的一个场景编写代码,但是看到它已经揭示了更大的问题,这可能发生在其他场景中,尽管它不太可能发生。
要理解这一点,请考虑两个人在Twitter上完全相同的时刻。他们都点击“关注”,导致在他们之间创建关系对象,然后他们的每个记录都被touch
编辑。如果这些接触成为交织:
每个进程都使用数据库事务,因此会导致死锁。
我错了,这可能发生在我奇怪的批处理作业场景之外的正常应用程序操作中?如果我没错,有什么办法吗?我可以以某种方式将touch
es移到交易之外吗? (最后写的胜利无论如何都可以更新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)
答案 0 :(得分:3)
不确定是什么造成了这种死锁,但你可以在处理它们时对这两个记录添加一个悲观锁定,这将阻止另一个请求处理它们,直到锁定被释放,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
注意:传递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
然后控制器会进行正常交易,或者根本不进行,因为你不需要它
Follow.create!(follower: @u1, followee: @u2)
注意: #update_all
没有触发activerecord回调,如果您有任何after_update
方法,则可以直接在数据库上进行查询我想避免这种方法。