使用 ActiveRecord 取得資料筆數的方式有三種用法,分別是 count、length、size,各自由不同的實作去取得資料筆數,正確的使用可以避免不必要資料庫查詢。
以下範例為 GroupClass 底下有多堂 Lesson 課程
group = GroupClass.first
lessons = group.lessons.all
# count
使用 count 時,ActiveRecord 會產生像 Select count(*) From ...
的 COUNT SQL 到資料庫取回目前資料筆數,,在 Loop 內使用會有 N+1 Query 的問題。
lesson.count
# (0.4ms) SELECT COUNT(*) FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
lesson.count
# (0.4ms) SELECT COUNT(*) FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
# length
length 會根據目前 association 是否已經載入記憶做出不同行為,若還沒有載入會產生 SELECT xxx.* FROM ...
的 SELECT ALL SQL,若已經載入則會直接計算 association 的 Array 大小。
# 先用 length 將 Lesson 載入記憶體 ,lessons[0].id 就不會再重新執行 Query
lessons.length
# Lesson Load (0.5ms) SELECT `lessons`.* FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
lessons[0].id
=> 1
# 先用 lessons[0].id 將 Lesson 載入記憶體,lessons.length 也不會重新執行 Query
lessons = group.lessons.all # refresh lessons association
lessons[0].id
# Lesson Load (0.7ms) SELECT `lessons`.* FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 1
lessons.length
=> 5
size size 會根據目前 association 是否已經載入記憶體做出不同行為,與 length 不同點在於產生的 Query 是 COUNT SQL,且呼叫後不會把結果儲存至記憶體。
# 情境一: lessons 已存在於記憶體
lessons = group.lessons.all # refresh lessons association
lessons[0].id
# Lesson Load (0.7ms) SELECT `lessons`.* FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 1
lessons.size
=> 5
# 情境二: lessons 不存在於記憶體
lessons = group.lessons.all # refresh lessons association
lessons.size
# (0.4ms) SELECT COUNT(*) FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
# 由於 size 使用 SELECT COUNT(*),其他欄位並不存在於記憶體,會重新執行 SELECT SQL
lessons[0].id
# Lesson Load (0.6ms) SELECT `lessons`.* FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 1
# 情境三: 重複使用 size
# 與 count 相同
lessons.size
# (0.5ms) SELECT COUNT(*) FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
lessons.size
# (0.5ms) SELECT COUNT(*) FROM `lessons` WHERE `lessons`.`group_class_id` = 1
=> 5
# 整理
使用上要先瞭解目前的 association instance 是否已經將結果載入記憶體,不確定的情況下可以用 loaded?
來做檢查,再依據結果選擇要用 length
還是 count
就不會有太大的問題。
size 可以適用大部的狀況,但 size
並不是完美的通用解,若已經知道後續會取用到其他欄位,使用 length 可以多節省一次查詢。
另外 size 可以使用 counter_cache (opens new window) 在 table 建一個欄位,關聯刪減時會將欄位數量增減記錄起來,但是這樣的紀錄方式,有可能會隨著時間產生落差,像是人為的刪減或是批次操作沒有觸發計數,使用上要比較小心。
# references
- https://stackoverflow.com/questions/6083219/activerecord-size-vs-count/21615375#21615375