ruby-on-rails - ActiveRecord::Batches::find_each 线程安全吗?

标签 ruby-on-rails multithreading activerecord

引用:http://api.rubyonrails.org/classes/ActiveRecord/Batches.html .

是执行find_each线程安全?换句话说,我可以做类似的事情吗

count = 0
MyModel.find_each do |model|
    count += 1 if model.foo?
end

并期望它是线程安全的?

最佳答案

这个问题已经有一段时间没有答案了。我认为这是一个很好的问题,因为像这样的线程安全问题可能对应用程序的完整性有害,而且由于 Rails 感觉如此神奇,所以最好总是深入了解并了解发生了什么。

如果数据的状态可以在代码执行期间更改并影响结果,则此方法 ( find_each ) 在指定情况下将不是线程安全的。 (例如,使用删除的数据调用块,使用相同的数据调用两次块,以及跳过部分数据的块)。

总之,find_each 不是 线程安全。它不做任何锁定,所以它 确保在调用块时数据已被删除、更新、插入或移动。它唯一确保的是不会为同一个主索引调用该块两次。

这是一个可能会产生奇怪结果的示例(尽管这是愚蠢的情况)。让我们假设以下 Account table :

|id|balance|
| 1|   1000|
| 2|    500|
| 3|   2000|

以及以下代码(让我们使用 batch_size: 1 因为它是一个很小的表):
total = 0
Account.find_in_batches(batch_size: 1) |acc|
   total += acc.balance
end

在第一次迭代中,它将运行带有 Account(id: 1, balance: 1000) 的块,因此 total等于 1000 .
现在,在运行第二次迭代时,另一个线程运行以下代码:
Account.transaction do
    acc1 = Account.find(1).lock!
    acc3 = Account.find(3).lock!
    acc1.update(balance: acc1.balance + acc3.balance)
    acc3.update(balance: 0)
end

它基本上将所有内容从帐户 1 转移到帐户 3。现在该表将如下所示:
|id|balance|
| 1|   3000|
| 2|    500|
| 3|      0|

但是请记住,我们已经处理了第一个帐户,并且它会继续使用第二个帐户运行块,因此 total等于 1500 ,然后最后运行第三个帐户的块,因为余额现在是 0 , total将保持在 1500 .
这将导致您拥有 total1500当您显然想在 3500 上获得它时.

( each 不是完全线程安全的,但可以保证这种情况)

如果需要确保线程安全,一个简单的方法就是在表上加锁(例如在 postgres 中)。请记住,锁定整个表会极大地影响您的性能。
count = 0
MyModel.transaction do
   ActiveRecord::Base.connection.execute("LOCK TABLE mymodels SHARE")
   MyModel.find_each do |model|
       count += 1 if model.foo?
   end
end

请注意 MyModel.lock.find_each也不是线程安全的。
find_each通过按主索引(通常是 id )对所有内容进行排序,并使用批量大小(默认为 1000 )限制结果。
SELECT  "models".* FROM "models" WHERE "models"."id" > 1000) ORDER BY "models"."id" ASC LIMIT $1

它存储批处理中的最后一个 id,然后为每一行调用块。一旦对每一行执行该块,它就会使用 models.id > last_id 运行另一个查询。 , 直到结束。

关于ruby-on-rails - ActiveRecord::Batches::find_each 线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29501684/

相关文章:

ruby-on-rails - 渴望加载多态

ruby-on-rails - 为什么我的 rspec-rails 生成的规范由于路由异常而失败?

ruby-on-rails - has_many 通过具有唯一来源的多个模型

ruby-on-rails - 子目录 list 文件的 Assets 预编译

ruby-on-rails - 在 child 协会上使用地理编码器,如何在给定位置查找所有 parent ?

java - 如何在 Java 中调用具有单独线程的方法?

spring - Web 应用程序中的多线程和并行处理 - 需要建议

java - Android套接字聊天连接错误

activerecord - 如何从 Rails 的查询中排除 id 数组(使用 ActiveRecord)?

ruby-on-rails - 关于空 belongs_to 关联的最佳实践