← [ BACK_TO_NOTES ]

N+1 Query Problem

N+1 Query Problem 是先查 N 个主体,再为每个主体各发一次关联查询,导致数据库往返次数从常数级膨胀到 1+N。

#concept #growing #backend #database

[!info] related notes

N+1 Query Problem

一句话定义

N+1 Query Problem 指一次主查询得到 N 个对象后,又为每个对象单独查询关联数据,最终产生 1 + N 次数据库访问。

最小场景

const users = await User.findAll()
for (const user of users) {
  user.assessments = await user.getAssessments()
}

如果有 100 个 User,可能变成:

SELECT users            1 次
SELECT assessments      100 次
-----------------------------
总计                    101 次

问题不在循环本身,而在循环体触发了数据库 I/O。

为什么 ORM 容易隐藏它

ORM 把关系访问包装成 user.getAssessments()、resolver、property 等自然 API,代码看起来像普通对象访问,但每一次可能都对应独立 SQL 和网络往返。

常见解决策略

Eager / Preload

提前声明需要关系,由 ORM 使用 JOIN 或少量批量查询获取。

Batch Query

先得到主体 ID:

1, 2, 3, ..., N

再一次查询:

SELECT *
FROM assessments
WHERE user_id IN (...);

总查询数从 1 + N 降到常数级。

JOIN

适合关系可以自然在数据库层组合的场景,但要注意 1:N 导致的结果行放大和分页语义。

DataLoader / 批处理层

GraphQL Resolver 等逐字段执行模型里,可把同一批次的单键查询合并成批量读取。

不要误解成“必须一条 SQL”

目标不是把所有数据塞进一个巨大 JOIN,而是避免查询次数随主体数量线性增加。两三次有计划的 batch query 往往比一条复杂 JOIN 更清晰、更稳定。

如何发现

  • 开启 ORM / SQL query log;
  • 看 APM trace 中相同 SQL 是否重复大量出现;
  • 关注“查询列表后循环访问关联”的代码;
  • 用数据量从 10 增到 100/1000 时观察 query count 是否同步增长。

边界

查询一个单独 User 后再查一次 Assessments 是固定两次查询,不属于典型 N+1;N+1 的核心是查询次数随结果集大小 N 增长。

创建于 2026/9/14 更新于 2026/9/14
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本