N+1 Query Problem
前言
最近剛從 NET 6 跳來 Rails,免不了要踩到一些新手必踩的坑。像是這個 N+1 查詢問題就是很常見的坑…
尤其對我這個用 EF 習慣的人,跳到 ActiveRecord 真的是各種不適應。不過不是不好用的不適應,而是那種方便到很沒有安全感的那種便利XD
問題描述
假設你有一個 Customer 和他們的 Order 資料表。當你查詢 Customer 資料時,如果每個 Customer 都有多個 Order,而你同時查詢這些 Order,就會發生 N+1 的查詢問題。
所以當你執行的一個查詢,實際上會有下面兩個動作:
- 先執行一個查詢來取得所有 Customer。
- 然後對
每個
Customer 執行一個查詢來取得該 Customer 的 Order。
如果有 N 個 Customer,你將會執行 1 + N 個查詢,這會導致效能問題。
簡單來說,N+1 查詢問題就是當你需要多次查詢資料庫才能取得完整資料時,會造成大量不必要的查詢,影響效能。
解決方法
includes
1 | Customer.includes(:orders).all |
把需要的資料一次撈出來,轉換成 SQL 語法的話就是使用了 IN (‘1’,’2’,’3’) 這樣的方式來一次性預先載入。避免重複向資料庫存取。
eager_load
1 | Customer.eager_load(:orders).all |
- eager_load 強制使用 LEFT OUTER JOIN 來載入關聯資料。在一個查詢中同時載入主要資料和關聯資料。
- 適合在需要對關聯資料進行條件篩選或排序時使用。
preload
1 | Customer.preload(:orders).all |
preload 強制使用多次查詢來載入關聯資料。它會先查詢主要資料,再查詢關聯資料,這樣可以避免 JOIN 查詢的開銷。
適合在不需要對關聯資料進行條件篩選時使用。
上述提到的多次查詢
是會轉換成下面這樣的 SQL 語法:
1 | -- 查詢顧客 |
總結
一般來說都是直接選擇使用includes
,因為 includes 會選擇最優的查詢策略。它會根據上下文決定是使用單獨查詢還是 JOIN 查詢。
當只讀取資料而沒有條件篩選時,通常會進行兩次查詢(preload)。
當涉及條件篩選並需要 JOIN 查詢時,它會自動使用 LEFT OUTER JOIN(eager_load)。
- 標題: N+1 Query Problem
- 作者: Larry Lai
- 撰寫于 : 2024-07-17 18:00:00
- 更新于 : 2024-07-18 09:38:06
- 連結: https://redefine.ohevan.com/2024/07/17/n-plus-one-problem/
- 版權宣告: 本作品采用 CC BY-NC-SA 4.0 进行许可。
留言