
从查询日志锁定问题
最直接的入口是数据库查询日志。在开发环境监听每条 SQL 与耗时并写入日志,再请求一次接口,看 SQL 条数是否随返回数据量线性增长。
一个列表接口如果返回二十条记录却执行了四十多条查询,基本可以确定是关联查询逐条触发。特征很明显:一条主查询加若干条结构相同、只有主键不同的子查询。
生产环境不方便开全量日志时,可以借助慢查询日志或应用内的查询统计,先按耗时排序找出最贵的接口,再回到开发环境复现。记录时带上路由与调用来源,便于把慢查询归到具体接口。
用预加载替代循环查询
关联查询默认是懒加载,在循环里访问关联对象的属性,就会每条记录触发一次查询。预加载能把它们合并成一条按主键集合过滤的查询。
嵌套关联同样可以预加载,用点号连接层级。预加载不是越多越好,关联表数据量大且只需要少数字段时,应该显式限定列,避免把整行读出来。
预加载之后的计数、求和这类聚合,直接在关联上算,比先查出来再在应用层遍历要省内存也省往返。预加载的关联如果带条件过滤,条件要写在预加载闭包里,而不是事后在集合上过滤。
注意延迟加载的隐蔽触发
访问器和接口序列化层里访问关联,同样会触发懒加载,而且往往藏得很深。序列化类里多写一行字段,就可能让整个列表慢下来。
可以开启模型的防止懒加载开关,在开发环境直接抛异常,把隐式懒加载暴露出来。上线前再按需要放行确属合理的场景。
集合上判断某个关联是否存在,也可能引起逐条查询。这种判断用关联存在性查询,或者在预加载后的集合里做匹配更合适。
分批处理与游标
导出、同步任务里一次性把整表查进内存再遍历,既是内存问题也是查询问题。用主键分批或游标方式处理,每批控制在一个合理规模。
按偏移量分页在并发写入时会漏记录或重复;以主键为游标的方式稳定性更好,前提是主键单调且不重复。
游标模式内存占用最低,但同一连接上不能并发执行其他查询,适合纯读取的后台任务。需要边读边写时用分批而不是游标。每批处理完要能记录断点,任务中断后可从上次位置继续,避免整批重跑。
把查询数量纳入检查
在测试里对关键接口断言查询条数,超过预期就失败。这条断言比响应时间更稳定,也不受机器性能波动影响。
配合接口返回的数据量做参数化测试,覆盖空列表、单条、整页三种情况。逐条触发的查询在空列表时不会暴露,只测一种数据量很容易漏。
代码评审时看到循环体内出现数据库访问或远程调用,就应该停下来问一句能否提到循环外批量处理。


