【发布时间】:2018-02-19 14:55:31
【问题描述】:
假设我需要确保ModelName 不能被两个不同的 Rails 线程同时更新;例如,当一个 webhook 发布到应用程序尝试修改它,同时一些其他代码正在运行时,就会发生这种情况。
根据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 可能正在读取陈旧的数据,这将是线程 1 在完成已经运行的 with_lock 块后很快就会更新它,因为线程 2 可以读取模型,而 with_lock 块在线程 1 中正在进行,它只能因为锁而无法更新它。
编辑:我找到了这个问题的答案,所以你可以停止阅读这里并直接进入下面:)
我的一个想法是开始 with_lock 块发布对模型的无害更新(例如 model_instance_name.update_columns(updated_at: Time.now)),然后使用 model_name_instance.reload 跟随它以确保它得到最新的更新数据。因此,如果两个线程同时运行相同的代码,那么只有一个线程能够发出第一个更新,而另一个线程需要等待锁被释放。一旦它被发布,它会跟着model_instance_name.reload 确保得到其他线程执行的任何更新。
问题是这个解决方案对我来说似乎太老套了,我不确定我是否应该在这里重新发明轮子(我不知道我是否遗漏了任何边缘情况)。如何确保当两个线程同时运行完全相同的方法时,一个线程等待另一个线程完成甚至读取模型?
【问题讨论】:
-
能否展示部分代码,尤其是对数据库的操作?
标签: ruby-on-rails activerecord