参考:http://api.rubyonrails.org/classes/ActiveRecord/Batches.html。
find_each
的实现是否是线程安全的?换句话说,我可以做一些像
count = 0
MyModel.find_each do |model|
count += 1 if model.foo?
end
并期望它是线程安全的吗?
答案 0 :(得分:3)
这个问题暂时没有答案。我认为这是一个非常好的问题,因为像这样的线程安全问题可能会对应用程序的完整性造成损害,而且由于Rails感觉如此神奇,所以总是深入了解并了解正在发生的事情。
如果在执行代码期间可以更改数据的状态并影响结果,则此方法(find_each
)在指定的情况下不是线程安全的。 (例如,使用删除的数据调用块,使用相同的数据调用块两次,并跳过部分数据)。
总之,find_each
不是线程安全的。它不执行任何锁定,因此不确保在调用块时数据已被删除,更新,插入或移动。它唯一确保的是不会为同一个主索引两次调用该块。
这是一个可以产生奇怪结果的例子(虽然是愚蠢的情况)。我们假设以下Account
表:
|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
。
当您明确尝试在total
处获得1500
时,这会导致3500
each
。
(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
不是完全线程安全的,但它保证了这种情况)
如果您需要确保线程安全,一种简单的方法是锁定表(例如,在postgres中)。请记住,锁定整个桌面会对您的表现造成很大影响。
MyModel.lock.find_each
请注意,find_each
也不是线程安全的。
id
通过主索引(通常为1000
)对所有内容进行排序,并使用批量大小限制结果(默认为SELECT "models".* FROM "models" WHERE "models"."id" > 1000) ORDER BY "models"."id" ASC LIMIT $1
)。
models.id > last_id
它存储批处理中的最后一个id,然后为每一行调用块。一旦为每一行执行了块,它就会使用import bs4 as bs
import urllib.request
source = urllib.request.urlopen('https://pythonprogramming.net/parsememcparseface/').read()
soup = bs.BeautifulSoup(source,'lxml')
print(soup.get_text())
运行另一个查询,直到它到达结尾。