这是一篇关于 N+1 查询问题及相关解决方案的长文,主要内容如下:
- N+1 问题概述:以
Product
模型为例,render
方法中部分属性直接来自模型,部分需通过关联记录加载。在循环渲染 10 个产品时,初始查询 1 次,每个产品懒加载owner
又会产生 10 次查询,即“N+1”问题,实际中情况更复杂会变成“N*M+1”,数据库虽快但此问题仍常见,ORM 虽有预加载功能但仍存在问题。 - 用 Ruby fibers 解决 N+1:在 Stripe 架构中,由于基于 Mongo 且记录常通过点索引加载,N+1 问题普遍。创新方案是用 Ruby 的
fibers
,在自定义#render_*
重载方法中,将其包裹在fiber
中,若调用数据库层则暂停,记录意图并启动下一个fiber
,所有fiber
处理完后聚合数据库意图为批量操作,此方案虽有局限性但能显著降低 API 调用延迟。 - Rails 的严格加载:Rails 6.1 引入“严格加载”,禁止懒加载,测试时若代码执行懒加载则会失败,可在部署前消除所有懒加载实例,这是重要创新但不是万能的,需有足够的测试覆盖。
- Go 中的数据加载:Go 语言较灵活但加载数据困难,即使不考虑 N+1 问题也很复杂,默认结果是大量样板代码,增加的冗长性并不能减少 N+1 问题的发生,且难以修复,作者曾修复过一个经典的 N+1 问题,解决方法是在循环前一次查询多个资源,代码量较大。
- 两阶段加载和渲染:这是一种通用的数据加载模式,分为加载阶段和渲染阶段。加载阶段生成包含渲染任意数量资源所需数据的“加载包”,渲染阶段使用加载包渲染单个资源,禁止数据库访问。实现此模式涉及较多代码,但能有效避免 N+1 问题,对于子资源也可通过组合加载包来处理,此模式可推广到其他语言,文中代码主要用于提供灵感。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。