ActiveRecord触摸导致死锁

时间:2015-03-17 06:22:57

标签: ruby-on-rails activerecord

我的应用程序广泛使用touch以利用Rails的模板缓存系统。当批量中的许多不同对象之间创建了许多关系时,我的应用程序会执行某种类型的工作。有时,某些工作会导致导致级联touch导致死锁。

我可以针对我经常看到它的一个场景编写代码,但是看到它已经揭示了更大的问题,这可能发生在其他场景中,尽管它不太可能发生。

要理解这一点,请考虑两个人在Twitter上完全相同的时刻。他们都点击“关注”,导致在他们之间创建关系对象,然后他们的每个记录都被touch编辑。如果这些接触成为交织:

  1. 流程1触及用户A
  2. 流程2触及用户B
  3. 流程1触及用户B
  4. 流程2触及用户A
  5. 每个进程都使用数据库事务,因此会导致死锁。

    我错了,这可能发生在我奇怪的批处理作业场景之外的正常应用程序操作中?如果我没错,有什么办法吗?我可以以某种方式将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)
    

1 个答案:

答案 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方法,则可以直接在数据库上进行查询我想避免这种方法。