前言
很早以前接觸資料庫就有聽說過「N+1 問題」,不過一直沒有寫下筆記認真思考過一次,這次撰寫問題成因與詳細解方與圖表。
N+1 問題是什麼?
「N+1 問題」主要指在與資料庫查詢一次 1 次時,之後針對其 N 個項目的每一個再分別執行 N 次關聯查詢,這樣的邏輯容易造成嚴重的效能問題。
- 1:一次查詢找出所有吻合特定條件的資料
- N:根據 1 的結果,再針對每筆資料查詢其關聯資料
怎麼解決 N+1 問題?
問題在於批次且大量的訪問資料庫,如果可以改成一次性索取必要資料就能解決該問題。
- 資料結構設計(去正規化)
- 在 DB 層完成關聯資料查詢:
$lookup或JOIN
db.users.aggregate([ { $lookup: { from: "posts", localField: "_id", foreignField: "userId", as: "posts" } }])- 批次查詢並在應用層組裝:
$in或IN
// 步驟 1: 查詢所有文章const posts = await Post.find(); // 100 篇文章
// 步驟 2: 收集所有作者 IDconst userIds = [...new Set(posts.map(p => p.userId))];
// 步驟 3: 批次查詢所有作者 (只執行 1 次)const users = await User.find({ _id: { $in: userIds } });
// 步驟 4: 在應用層組裝const userMap = new Map(users.map(u => [u._id, u]));posts.forEach(post => { post.author = userMap.get(post.userId);});- ORM ODM Eager Loading
總結
- 避免在迴圈中對資料庫進行查詢。
- 了解 ORM 如何下查詢:許多 ORM 預設使用 Lazy Loading 也可能生成有 N+1 問題的低效率查詢,通常也有對應的解決方案可以多翻閱文件了解相關設定才能使 ORM 生成較有效率的查詢。