← 中文资源

简体中文版本

N+1 查询问题

N+1 问题先查一个列表,再为每一行额外查询。预加载、连接或批量查询可以把往返次数降为常数。

阅读英文原文

模式

// ❌ 先查用户,再为每个用户查一次
const users = await db.user.findMany();
for (const user of users) {
  user.posts = await db.post.findMany({ where: { authorId: user.id } });
}

100 个用户会产生 101 次查询。即使每次 2ms,请求的大部分时间也花在往返上。

修复

// ✅ 一次查询并加载关联
const users = await db.user.findMany({ include: { posts: true } });
-- ✅ 集合式连接
SELECT u.id, u.name, p.id AS post_id, p.title
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
WHERE u.id = ANY($1);

对远程 API,用批量端点或在同一事件循环 tick 内合并键的 DataLoader 式批处理替换逐项调用。

何时使用哪种

当关联较小且总是需要时,预加载方便;集合式读取用连接高效,但可能重复父行;批量查询适合跨服务边界或关联数据来自另一个系统的情况。

验证

在测试或开发环境统计每请求查询数。列表端点的查询数不应随行数线性增长。比较 10、100、1,000 行的延迟;N+1 在 10 行时看起来正常,在 1,000 行时会失败。

陷阱

无条件预加载全部关联会制造新的性能问题;分页加预加载也可能产生巨大载荷。同时测量查询数与数据量,避免无界的 include 图。