N+1 查询:每条都很快,加起来很慢
先 1 条查出订单列表,再对每个订单各查 1 次用户,一共 1 + N 条。每条都快,所以慢查询日志里抓不到,但网络往返和解析开销叠起来很可观。
它不只是 ORM 的问题,根子是在循环里查询:手写 SQL 的 DAO 被循环调用、微服务里逐个调 RPC、GraphQL 的 resolver,都会这样。ORM 只是让它更隐蔽,order.user 看起来像读一个属性。
改法是批量取:
SELECT * FROM user WHERE id IN (1, 2, 3, ...);ORM 一般有三类办法:
- 显式预加载:Django
select_related/prefetch_related、Railsincludes、SQLAlchemyselectinload、GORMPreload。 - 让懒加载直接报错:Rails
strict_loading、LaravelpreventLazyLoading、SQLAlchemylazy="raise"。开发环境打开,N+1 一出现就报错。 - 设计上不提供隐式懒加载:比如 Prisma 必须显式写
include。
Redis 同理:循环里一个个 GET,改成 MGET 或 pipeline。