Ruby on rails Rails/ActiveRecord:避免两个线程同时使用锁更新模型
假设我需要确保两个不同的Rails线程不能同时更新Ruby on rails Rails/ActiveRecord:避免两个线程同时使用锁更新模型,ruby-on-rails,activerecord,Ruby On Rails,Activerecord,假设我需要确保两个不同的Rails线程不能同时更新ModelName;例如,当webhooks发布到应用程序时,在运行其他代码的同时尝试修改它,就会发生这种情况 根据,我认为解决方案是使用model\u name\u instance.with\u lock,这也会开始一个新的事务 这可以很好地工作,并防止同时更新模型,但它不会阻止其他线程在运行带锁定的块时读取该表行 我可以通过这样做证明带锁的不会阻止其他读取: 打开2个导轨控制台 在控制台1上,键入类似于ModelName.last.wi
ModelName
;例如,当webhooks发布到应用程序时,在运行其他代码的同时尝试修改它,就会发生这种情况
根据,我认为解决方案是使用model\u name\u instance.with\u lock
,这也会开始一个新的事务
这可以很好地工作,并防止同时更新模型,但它不会阻止其他线程在运行带锁定的块时读取该表行
我可以通过这样做证明带锁的不会阻止其他读取:
- 打开2个导轨控制台
- 在控制台1上,键入类似于
ModelName.last.with_lock{sleep 30}
- 在控制台2上,键入
ModelName.last
。你可以很容易地读懂模型
- 在控制台2上,键入
ModelName.update\u列(更新时间:Time.now)
。您将看到它将等待30秒的锁过期,然后才完成
这证明了锁不会阻止读取,而且据我所知,无法锁定数据库行以防止读取
这是有问题的,因为如果两个线程在完全相同的时间运行相同的方法,并且我必须决定运行with_lock
块来检查模型数据,线程2可能正在读取过时的数据,这些数据将在线程1完成已运行的带锁
块后很快由线程1更新,因为线程2可以读取模型,而带锁
块在线程1中进行,它只能因为锁而无法更新
编辑:我找到了这个问题的答案,所以你可以停止阅读,直接转到下面:)
我的一个想法是在开始时使用_lock
块对模型进行无害的更新(比如model\u instance\u name.update\u columns(更新时间:Time.now)
),然后使用model\u name\u instance.reload
来确保它获得最新的数据。因此,如果两个线程同时运行相同的代码,则只有一个线程能够发出第一次更新,而另一个线程则需要等待释放锁。一旦发布,它后面会跟着model\u instance\u name.reload
,以确保获得其他线程执行的任何更新
问题是这个解决方案对我来说太老套了,我不确定我应该在这里重新发明轮子(我不知道我是否遗漏了任何边缘案例)。当两个线程在完全相同的时间运行完全相同的方法时,如何确保一个线程等待另一个线程完成甚至读取模型?如果您很接近,您希望使用乐观锁定而不是悲观锁定:
它不会阻止读取对象和提交表单。但是它可以检测到表单是在用户看到对象的过时版本时提交的。感谢Robert提供的乐观锁定信息,我肯定可以看到我走了这条路,但是乐观锁定通过在写入数据库时引发异常(SQL更新)来工作,我有很多复杂的业务逻辑,我甚至不想在一开始就用陈旧的数据运行这些逻辑
我就是这样解决的,比我想象的要简单
首先,我了解到悲观锁定不会阻止任何其他线程读取该数据库行
但我还了解到,无论您是否尝试写入,带锁的也会立即启动锁
因此,如果启动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的锁保持,即使它没有发出任何写入操作
因此,这是一种“锁定读取”的方法:如果您有一段代码,您希望确保它不会同时运行(甚至不用于读取!),则从该方法开始,使用_lock
块打开,并在其中发出模型读取,它们将等待任何其他锁定首先被释放。如果您在它之外发出读取,那么即使另一个线程中的其他代码段在该表行上有锁,也会执行读取
我还学到了其他一些好东西:
- 根据,
with_lock
不仅会使用锁启动事务,还会为您重新加载模型,因此您可以确保块ModelName.last
处于最新状态,因为它会在该实例上发出.reload
- 这是一些专门为阻止同一段代码同时在多个线程中运行而设计的gem(我相信大多数Rails应用程序在生产环境中运行时都是这样),而不考虑数据库锁。看看redis互斥、redis信号量和redis锁
- web上有很多文章(我至少可以找到3篇)指出Rails
和_lock
将阻止对数据库行的读取,而通过上面的测试我们可以很容易地看到情况并非如此。小心,并始终确认自己测试的信息!我试图对他们发表评论,并就此提出警告
您能否显示部分代码,尤其是DB的操作?阻止读取实际上并不重要