NOTEBackend

先过滤,再排序与聚合:把 27 秒分页查询降到 1 秒级

本文结论

通过把高选择性业务过滤前移到窗口函数和关联聚合之前,将一个 Oracle 分页查询从 27.49 秒降到约 1.40 秒。

一个分页业务页面只返回约 10 KB 数据,却需要 27.49 秒。响应体很小,基本可以排除网络传输是主要瓶颈;但浏览器耗时本身还不能说明延迟来自页面渲染、应用代码,还是数据库。

真正有用的证据是:一次页面请求会执行两条高成本 SQL,一条用于计算分页总数,另一条用于取得当前十行数据。数据库统计显示,计数 SQL 平均约 21.3 秒,读取约 448 万个缓冲块;数据 SQL 还需要 7.8 至 9.0 秒。两者相加,几乎完整解释了前端观测到的耗时。

改写前的证据

原查询在知道用户实际需要哪些订单之前,就先构造了多个派生数据集:对大范围流转记录执行窗口排序,聚合明细和质检记录,最后才按组织、项目、近期订单日期和高级搜索条件过滤订单。

这使“每页十条”产生了错觉。分页只限制最终返回数量,并没有限制前置公共表表达式的工作量。数据库仍要对广泛的历史数据执行两遍高成本处理:一遍计算总数,另一遍取得当前页。

同一页面附近已有一条汇总查询可以作为对照。它先筛出当前范围内的近期订单,再关联依赖数据,在相同环境下不到一秒即可完成。这个差异说明主要问题是过滤条件所在的层级,而不是笼统的网络抖动或数据库容量不足。

根因

根因不只是“缺索引”或“关联太多”,而是高选择性的业务条件被放在最终结果层,没有进入高成本计算的输入端。

这对三类 SQL 结构影响尤其明显:

  • 窗口函数必须为进入其输入集的每一行排序;
  • 聚合必须读取并分组所有匹配明细;
  • 分页计数查询虽然只返回一个数字,却会重复其中大部分工作。

当过滤发生在这些操作之后,面对复杂关联、可选条件和窗口表达式时,优化器未必能把每个谓词安全地下推。查询逻辑仍然正确,但工作集远大于实际请求需要。

先确定候选集

改写后的查询以 filtered_orders 公共表表达式作为边界。租户范围、业务归属、默认近期日期窗口和高级过滤条件,都在窗口排序与聚合之前执行。

所有依赖数据随后只关联这个候选集:

  1. 先选出符合本次请求的订单标识;
  2. 只对这些订单的流转记录执行排序;
  3. 只聚合这些订单的包装、质检和明细数据;
  4. 继续组装原有响应字段与状态规则;
  5. 保持排序和分页规则不变。

入库与出库查询采用相同结构。只有在关联既不提供返回字段、也不参与过滤时才移除它。本次没有修改数据库结构、接口参数、控制器、响应类型或前端行为。

这不是单纯把 SQL 换成 CTE 写法。候选集先把后续计算限制在已筛选订单内,明确控制了需要处理的数据量,避免计算再次扩张到整张历史表。

验证

优化后的映射先通过 XML 解析和 Java 8 构建,再由本地部署的应用使用与基线相同的只读生产查询进行复测。

入库链路的结果是:

  • 浏览器请求从 27.49 秒降到 1.40 秒,降低约 94.9%;
  • 连续三次数据库核心 SQL 合计耗时约为 1.320、0.750 和 0.602 秒;
  • 中位数约为 0.750 秒;
  • 计数 SQL 的缓冲读取降低约 98.9%;
  • 数据 SQL 的缓冲读取降低约 94.8%。

出库链路连续三次均正常返回预期的十行数据,两条核心 SQL 合计平均约 1.18 秒。

验证时刻意把浏览器耗时和数据库耗时分开。一次更快的页面截图只能说明现象变好;连续数据库测量才能证明优化来自实际工作量下降,而不是偶然命中热缓存。

经验与限制

LIMITROWNUM 或其他分页包装限制的是返回行,不一定限制实际处理行。查询包含窗口函数、大范围聚合或昂贵的计数伴随查询时,应重点检查高选择性条件在执行链路中的位置。

一套可复用的优化顺序是:

  1. 分别测量计数 SQL 与数据 SQL;
  2. 找到本次请求最小且稳定的业务主键集合;
  3. 让高成本派生数据在计算前先关联这个集合;
  4. 保持响应语义,并用重复执行对比结果;
  5. 在工作集和关联形状合理之后,再评估索引。

这种方法最适合具有租户、组织、日期或精确标识等高选择性范围的请求,不适合本就需要无界扫描的分析查询。可选搜索条件也必须单独回归,因为条件前移如果处理不当,可能改变空值或一对多关联语义。只有分页总数、排序、字段值和边界筛选保持一致时,性能提升才算真正通过验收。

分类Backend
遇到类似系统问题?

先说明系统,再说明症状

如果需要生产排障、DevOps 交付或物流系统集成协作,请提供当前表现、预期结果、受影响环境、可用日志或数据样例,以及发布限制。我会从现有证据开始判断。

通过邮件开始