參考:http://api.rubyonrails.org/classes/ActiveRecord/Batches.html。是ActiveRecord :: Batches :: find_each線程安全嗎?
是否執行find_each
線程安全?換句話說,我可以做類似
count = 0
MyModel.find_each do |model|
count += 1 if model.foo?
end
並期望它是線程安全的?
參考:http://api.rubyonrails.org/classes/ActiveRecord/Batches.html。是ActiveRecord :: Batches :: find_each線程安全嗎?
是否執行find_each
線程安全?換句話說,我可以做類似
count = 0
MyModel.find_each do |model|
count += 1 if model.foo?
end
並期望它是線程安全的?
這個問題一直沒有答案。我認爲這是一個非常好的問題,因爲像這樣的線程安全問題可能會損害應用程序的完整性,並且由於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
會[R留在1500
。 當您明確嘗試使用3500
時,這會導致您擁有total
在1500
。
(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
,直到它結束。
什麼是downvote? – franklsf95