前言
很早以前接触资料库就有听说过「N+1 问题」,不过一直没有写下笔记认真思考过一次,这次撰写问题成因与详细解方与图表。
N+1 问题是什么?
「N+1 问题」主要指在与资料库查询一次 1 次时,之后针对其 N 个项目的每一个再分别执行 N 次关联查询,这样的逻辑容易造成严重的效能问题。
- 1:一次查询找出所有吻合特定条件的资料
- N:根据 1 的结果,再针对每笔资料查询其关联资料
怎么解决 N+1 问题?
问题在于批次且大量的访问资料库,如果可以改成一次性索取必要资料就能解决该问题。
- 资料结构设计(去正规化)
- 在 DB 层完成关联资料查询:
$lookup或JOINDB 層完成關聯資料查詢:$lookup或JOIN
db.users.aggregate([ { $lookup: { from: "posts", localField: "_id", foreignField: "userId", as: "posts" } }])- 批次查询请在应用层组装:
$on或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);});
userMap.get(userId)- ORM ODM Eager Loading
总结
- 避免在循环中对资料库进行查询。
- 了解 ORM 如何下查询:许多 ORM 预设使用 Lazy Loading 也可能生成有 N+1 问题的低效率查询,通常也有对应的解决方案可以多翻阅文件了解相关设定才能使 ORM 生成较有效率的查询。