Rails / ActiveRecord:使用锁

时间:2018-02-19 14:55:32

标签: ruby-on-rails activerecord

假设我需要确保ModelName不能同时由两个不同的Rails线程更新;例如,当一个webhooks发布到应用程序尝试在其他一些代码运行的同时修改它时,就会发生这种情况。

根据Rails documentation,我认为解决方案是使用model_name_instance.with_lock,这也会开始新的交易。

这样可以正常工作并防止同时向模型更新,但它不会阻止其他线程在with_lock块运行时读取该表行。

我可以通过这样做来证明with_lock不会阻止其他READS:

  • 打开2个导轨控制台;

  • 在控制台1上,键入ModelName.last.with_lock { sleep 30 }

  • 之类的内容
  • 在控制台2上,键入ModelName.last。你将能够阅读模型没问题。

  • 在控制台2上,键入ModelName.update_columns(updated_at: Time.now)。您将看到它将等待30秒锁定到期之前到期。

这证明锁定不会阻止读取,据我所知,没有办法锁定数据库行被读取。

这是有问题的,因为如果2个线程在同一时间运行相同的方法并且我必须决定运行with_lock块关于模型数据的某些先前检查,则线程2可能正在读取陈旧数据在完成已经运行的with_lock块之后,线程1将很快更新,因为线程2可以在线程1中with_lock块正在进行时读取模型,它只能更新因为锁定。

编辑:我找到了这个问题的答案,所以你可以在这里停止阅读并直接进入下面的内容:)

我的一个想法是开始with_lock阻止对模型发出无害更新(例如model_instance_name.update_columns(updated_at: Time.now)),然后使用model_name_instance.reload跟随它以确保它获得了最新的数据。因此,如果两个线程同时运行相同的代码,则只有一个线程能够发出第一个更新,而另一个线程需要等待释放锁定。一旦它被释放,它将跟随model_instance_name.reload,以确保获得由另一个线程执行的任何更新。

问题是这个解决方案对我来说似乎太过苛刻了,我不确定我应该在这里重新发明轮子(我不知道我是否缺少任何边缘情况)。如何确保当两个线程在同一时间运行完全相同的方法时,一个线程等待另一个线程完成甚至读取模型?

2 个答案:

答案 0 :(得分:8)

感谢Robert提供乐​​观锁定信息,我绝对可以看到我走这条路,但乐观锁定通过在写入数据库的时刻引发异常(SQL UPDATE),并且我有很多复杂的业务逻辑我甚至不想首先使用过时的数据运行。

这就是我解决它的方式,它比我想象的更简单。

首先,我了解到悲观锁定不会阻止任何其他线程读取该数据库行。

但我也了解到,with_lock也可以在不管你是否尝试写作的情况下锁定锁定。

因此,如果您启动2个rails控制台(模拟两个不同的线程),您可以测试:

  • 如果您在控制台1上键入ModelName.last.with_lock { sleep 30 },在控制台2上键入ModelName.last,则控制台2可以立即读取该记录。

  • 如果您在控制台1上键入ModelName.last.with_lock { sleep 30 }而在控制台2上键入ModelName.last.with_lock { p 'I'm waiting' },则控制台2将等待控制台1的锁定保持,即使很难发出任何写入任何

这样就可以锁定阅读内容:如果您有一段代码,您希望确保它不会同时运行(即使对于读取也是如此) !),开始该方法打开with_lock块并发出模型读取内部,他们将等待任何其他锁定首先被释放。如果你在它之外发出你的读取,你的读取将被执行甚至很难在另一个线程中的一些其他代码片段锁定该表行。

我学到的其他一些好东西:

  • 这是一些专门设计用于阻止在多个线程中同时运行的同一段代码的宝石(我相信大多数Rails应用程序都在生产环境中),无论数据库锁定如何。看看redis-mutex,redis-semaphore和redis-lock。

  • 网上有很多文章(我发现至少有3篇)说Rails with_lock会阻止对数据库行的READ,而我们可以很容易地看到上面的测试&# 39;事实并非如此。小心,并始终确认自己测试信息!我试图评论他们对此的警告。

答案 1 :(得分:1)

你很接近,你想要乐观锁定而不是悲观锁定:http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html

它不会阻止阅读对象并提交表单。但是当用户看到对象的陈旧版本时,它可以检测到表单已提交。