fra*_*f95 4 activerecord multithreading ruby-on-rails
参考:http : //api.rubyonrails.org/classes/ActiveRecord/Batches.html。
是实现find_each线程安全的吗?换句话说,我可以做点什么
count = 0
MyModel.find_each do |model|
count += 1 if model.foo?
end
Run Code Online (Sandbox Code Playgroud)
并期望它是线程安全的吗?
这个问题已经有一段时间没有答案了。我认为这是一个非常好的问题,因为像这样的线程安全问题可能会损害应用程序的完整性,并且由于Rails感觉如此神奇,因此最好始终仔细研究一下并了解正在发生的事情。
find_each如果可以在执行代码期间更改数据状态并影响结果,则此方法()在指定的情况下不是线程安全的。(例如,使用已删除的数据调用块,使用同一数据调用两次块,以及跳过数据的一部分)。
总而言之,find_each 它不是线程安全的。它不做任何锁定,因此不能确保在调用该块时已删除,更新,插入或移动了数据。它唯一确保的是不会为相同的主索引两次调用该块。
这是一个可能产生奇怪结果的示例(虽然很愚蠢)。让我们假设下Account表:
|id|balance|
| 1| 1000|
| 2| 500|
| 3| 2000|
Run Code Online (Sandbox Code Playgroud)
和以下代码(batch_size: 1由于它是一个很小的表,所以请使用):
total = 0
Account.find_in_batches(batch_size: 1) |acc|
total += acc.balance
end
Run Code Online (Sandbox Code Playgroud)
在第一次迭代中,它将使用来运行块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
Run Code Online (Sandbox Code Playgroud)
它基本上将所有内容从帐户1转移到帐户3。现在该表如下所示:
|id|balance|
| 1| 3000|
| 2| 500|
| 3| 0|
Run Code Online (Sandbox Code Playgroud)
但是请记住,我们已经处理了第一个帐户,它将继续与第二个帐户一起运行该区块,所以total等于1500,然后最终为第三个帐户运行该区块,因为现在余额为0,所以total将保持在1500。这将导致你有total在1500当你显然试图把它的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
Run Code Online (Sandbox Code Playgroud)
请注意,这MyModel.lock.find_each也不是线程安全的。
find_each通过按主索引对所有内容进行排序(通常是id),并以批处理大小限制结果(默认值为1000)来工作。
SELECT "models".* FROM "models" WHERE "models"."id" > 1000) ORDER BY "models"."id" ASC LIMIT $1
Run Code Online (Sandbox Code Playgroud)
它存储批次中的最后一个ID,然后为每一行调用该块。一旦对每一行执行了该块,它将使用来运行另一个查询models.id > last_id,直到到达末尾。
| 归档时间: |
|
| 查看次数: |
746 次 |
| 最近记录: |