数据库

N+1 查询:每条都很快,加起来很慢

先 1 条查出订单列表,再对每个订单各查 1 次用户,一共 1 + N 条。每条都快,所以慢查询日志里抓不到,但网络往返和解析开销叠起来很可观。

它不只是 ORM 的问题,根子是在循环里查询:手写 SQL 的 DAO 被循环调用、微服务里逐个调 RPC、GraphQL 的 resolver,都会这样。ORM 只是让它更隐蔽,order.user 看起来像读一个属性。

改法是批量取:

sql
SELECT * FROM user WHERE id IN (1, 2, 3, ...);

ORM 一般有三类办法:

  • 显式预加载:Django select_related / prefetch_related、Rails includes、SQLAlchemy selectinload、GORM Preload。
  • 让懒加载直接报错:Rails strict_loading、Laravel preventLazyLoading、SQLAlchemy lazy="raise"。开发环境打开,N+1 一出现就报错。
  • 设计上不提供隐式懒加载:比如 Prisma 必须显式写 include。

Redis 同理:循环里一个个 GET,改成 MGET 或 pipeline。

esc