2017年5月29日 星期一

find_each & find_in_batches 用法介紹

寫在開始之前


Rails提供了蠻多好用的方法可以使用,只是很多時候不知道有這些方法,然後又自己繞了一圈去實作相同的方法。今天要講的是find_each & find_in_batches 這兩個批次處理的方法。所謂的批次處理代表的是不要一開始就將所有要處理的記錄一次拿出來,而是分次拿出來處理,如此一來最大的好處就是「節省記憶體」,但原理是?

當我將記錄從資料庫提取出來時,其實就是將「記錄全部實例化放在記憶體裡」,所以當紀錄的量愈大時,所佔用的記憶體就會愈大。因此如果量大到一個程度的話,結果就是...你知道的XD

想像一個情境,今天你架了一個部落格網站,裡面已經有了幾千篇的文章,而你需要修改其中一個欄位的值,那你該怎麼做?可以寫一隻rake,裡面呼叫所有的文章,並修改需要修改的欄位值,如何實作?讓我們繼續往下看。



提醒事項

  1. 在本文中寫給讀者看的註解會使用 % 符號 
  2. 如果是終端機的指令前面會加上$,輸入時只需要輸入這個符號後的程式碼

上工啦


1. 在 lib/tasks 資料夾新建一個 post.rake 

2. 假設我們要修改 posts table 裡面的 user_id 這個欄位,先來看原本的寫法

namespace :post do 
  desc "change user_id in all posts"
  rake :change_user_id_in_all_posts do
    posts = Post.all.
    posts.each { |post| post.update!(user_id: 2) }
  end
end
% 其中namespace為命名空間,為的是避免不同class間有相同的method導致打架。
% desc 是 description 的縮寫,裡面就是這個rake的描述。
% rake 的 do block 裡面就是這隻rake的主體了,我們將post一篇一篇叫出來,然後update其中的 user_id attribute。
% 但如果今天文章有幾千幾萬篇,抓出來的posts很可能讓記憶體炸掉,因此我們可以這麼寫....

namespace :post do
  desc "change user_id in all posts"
  rake :change_user_id_in_all_posts do
    Post.find_each(batch_size: 100) do |post|
      post.update!(user_id: 2)
    end
  end
end
% batch_size 是 find_each 的一個參數,其他詳細的參數可以上 這裡 查詢,batch_size 代表的是一次拿多少筆資料,如果是這樣寫的話每次只會處理100篇posts,就可以避免記憶體炸掉。
% find_each 也可以搭配不同的query,例如

Post.where(state: published).find_each(batch_size: 100) do |post|
  post.update!(user_id: 2)
end

至此,已經完成介紹了。咦,是嗎?那 find_in_batches 呢?其實和find_each還蠻相似的,只是他抓出來的資料會是group,例如

Post.find_in_batches(batch_size: 100) do |group|
  group.each { |post| post.update!(user_id: 2) }
end


完結

以上大致介紹了find_each & find_in_batches 的應用場景,如果需要更詳細的參數介紹的話可以看 這裡 。






    3 則留言 :