ActiveRecord的触摸导致死锁 [英] ActiveRecord touch causing deadlocks

查看:133
本文介绍了ActiveRecord的触摸导致死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序使用触摸广泛,以充分利用Rails的模板缓存系统。有某种类型的工作时,在一个批处理许多不同对象之间建立多对多的关系我的应用程序一样。有时候,一些这方面的工作成果所产生的级联触摸上课造成的僵局。

我可以code围绕这对于一个场景,我看到它经常发生,但看到它已经败露了更大的问题,这可能发生在其他情况下,尽管这是非常不可能的。

要理解这一点,想想两个人在Twitter上跟随另一个在完全相同的时刻。他们都点击关注,导致它们之间建立关系的对象,然后每个他们的记录是触摸编辑。如果这些接触成为交织:

  1. 在过程1触摸用户A
  2. 在处理2触摸用户B
  3. 在过程1触摸用户B
  4. 在过程中触碰2用户A

每个进程正在使用一个数据库事务,因此这将导致死锁。

我错了,这可能会在正常应用程序操作发生我奇怪的批处理作业的情况之外?如果我没有错,有没有什么解决办法吗?我能以某种方式移动触摸 ES是交易之外? (最后写WINS是罚款更新的updated_at反正...)

更新 - 数据模型的更多解释

 类跟踪
  belongs_to的:追随者,摸:真
  belongs_to的:followee,摸:真
结束

@ U1 = User.find(1)
@ U2 = User.find(2)

#后台作业1
Follow.create!(跟随者:@ U1,followee:@ U2)

#后台作业2
Follow.create!(跟随者:@ U2,followee:@ U1)
 

解决方案

不知道是什么让那个僵局,但你可以添加两个记录的悲观锁,而你处理它们,这将prevent从处理另一个请求他们,直到锁被释放,的ActiveRecord 将等待,然后再继续锁定释放。

  User.transaction办
  @ U1,U2 @ = User.lock.where(编号:[1,2])
  #这两个记录现在被锁定,其他交易实例
  #无法继续,直到本次交易退出块
  Follow.create!(跟随者:@ U1,followee:@ U2)
结束
#锁定在这里发布
 

注意:通过 ID:[2,1] 不会返回它们的顺序,所以你需要处理的状态。

注意2:太多的锁可能会影响你的整体应用程序的性能,因为用户模式可能是一个频繁使用的模式,但我想这一切都取决于这些多久如下发生。


更新:下面是一个也可能工作第二种方式,第一个后续机型,没有触动,而是一个 after_create

 类跟踪
  belongs_to的:跟随
  belongs_to的:followee

  after_create:touch_users

  高清touch_users
    #没有锁定和直接数据库更新
    User.where(编号:[follower.id,followee.id])。update_all(的updated_at:Time.now)
  结束
结束
 

然后,控制器会做一个正常的交易,或根本没有,因为你不需要它

  Follow.create!(跟随者:@ U1,followee:@ U2)
 

注意: #update_all 不火ActiveRecord的回调和查询在数据库中直接完成,如果您有任何 after_update 方法,那么你可能想避免这种方法。

My app uses touch extensively in order to take advantage of Rails' template caching system. There's a certain type of work my app does when many relationships are created between many different objects in a batch. Sometimes, some of this work results in the resulting cascading touches causing deadlock.

I can code around this for the one scenario where I am seeing it happen often, but seeing it has brought to light the larger problem, which could happen in other scenarios, albeit it's very unlikely.

To understand this, think about two people following one another on Twitter at exactly the same moment. They both click "Follow", resulting in the relationship objects being created between them and then each of their records being touched. If these touches become interweaved:

  1. process 1 touches user A
  2. process 2 touches user B
  3. process 1 touches user B
  4. process 2 touches user A

Each process is using a database transaction, so this will result in deadlock.

Am I wrong that this could happen in normal app operation outside of my weird batch job scenario? If I'm not wrong, is there any solution? Can I somehow move the touches to be outside of the transactions? (Last Write Wins is fine for updating updated_at anyway...)

update - more explanation of data models

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)

解决方案

Not sure what makes that deadlock but you could add a pessimistic lock on both records while you're handling them, this will prevent another request from handling them until the lock is released, ActiveRecord will wait for the lock release before proceeding.

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

Note: passing id: [2,1] won't return them in that order, so you'll need to handle that condition.

Note 2: Too much locking might affect your overall app performance, since the user model is probably a heavily used model, but I guess it all depends on how often these follows happen.


Update: Here's a second way that also might work, first the follow model, no touches, but instead an 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

Then the controller would do a normal transaction, or not at all, cause you don't need it

Follow.create!(follower: @u1, followee: @u2)

NOTE: #update_all doesn't fire the activerecord call backs and the queries are done directly on the database, if you have any after_update methods then you might want to avoid this method.

这篇关于ActiveRecord的触摸导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆