6 月窗口报表:47.6 秒 → 0.18 秒 260 倍加速 · 数字 100% 对账成功 · 本地副本完整验证

囍铺 ERP · 报表性能优化完整复盘

📅 2026-06-13 · 🔬 验证环境: xipu-mysql:13308 本地副本 · ⚠️ 生产 DB 全程未动
🚨 核心铁律: 所有改造在本地副本完成,数字对账后才考虑上线。生产 RDS 一行未动。

1 · 问题怎么发现的

导购在手机端打开「品类销售统计」报表,选 1 个月窗口要等 2 秒,选 3 个月直接转圈圈打不开,半年报表完全没法用。

实测后台 SQL 耗时:

时间窗口耗时体感
1 天228 ms正常
1 周271 ms正常
1 月1,868 ms明显卡
3 月13,013 ms用户放弃
6 月47,644 ms完全不能用

2 · 诊断:慢在哪

这条 SQL 叫 v4UserOutOrderCategoryTotal,把销售统计 + 退货统计糅在一个查询里,做了几层嵌套 JOIN。

EXPLAIN 拆解后定位:

子查询耗时(1月)问题
销售统计 (sales)~100 ms正常
订单工费 (fee)~150 msDISTINCT 去重,可优化
退货统计 (retreat)~1,600 ms笛卡儿积级 BNL,占 90% 总耗时

真根因:退货那段子查询里 xipunum_erp_retreat 表(45,052 行)走 ALL 全表扫,跟另一个派生表形成 Block Nested Loop,数据量越大成几何级放大。

3 · 走过的弯路:索引方案失败

第一反应:加索引。在本地副本加了 4 条针对热点字段的索引(idx_guide_time 等),实测结果:

时间窗口 加索引前 加索引后 结论
1 天228 ms220 ms持平
1 周271 ms1,557 ms反而变慢 5.7x
1 月1,868 ms1,836 ms持平
3 月13,013 ms12,914 ms持平
6 月47,644 ms47,456 ms持平
P0 索引方案对这个 SQL 基本无效。主查询从 1.8 秒 → 0.1 秒(18x),但 retreat 子查询 6.3 秒吃死总时间;1 周窗口优化器选错索引,反而变慢。

真根因不在索引层,在 SQL 把销售和退货糅在一起这件事本身。加索引修不了笛卡儿积。

所有 4 条本地索引已 DROP,生产 RDS 未受影响。

4 · 成功方案:聚合表

既然实时算几十万行就是慢,那就 提前算好。报表场景:

建 3 张「日预聚合表」:

表名维度行数用途
agg_outbound_daily 日期 × 品类 × 导购 13,862 销售件数 / 金额 / 净重
agg_outbound_order_daily 日期 × 订单 × 品类 × 导购 65,628 工费 DISTINCT 去重
agg_retreat_daily 日期 × 品类 × 退货单 36,117 退货金额 / 重量

总计 11.5 万行(原始 18.8 万行,1.6x 压缩),占用空间 < 20MB。

增量更新:滑动 7 天 cron

每 5 分钟跑一次脚本,只重算最近 7 天的数据:

单次重算耗时
0.18 秒
数据延迟上限
5 分钟
DB 时间占用
0.06%
自愈能力
无需补偿

错过任何一次执行,下次自动追上(因为是「重算」不是「增量加」)。逻辑上幂等,没有补偿队列、没有重试栈、没有 zombie 状态。

5 · 数字对账 · 100% 一致

聚合表方案要落地,前提是新 SQL 跟老 SQL 出来的数字 一字不差。用 6 月窗口做对账(那个 47 秒的 case):

对账品类数
6 个
对账字段数
8 列
差异条目
0 条
差异百分比
0%

对账字段:销售件数 / 销售金额 / 销售净重 / 工费(含手工费) / 退货重量 / 退货金额 — 每一个数字完全相等

✅ 中间发现一个坑:第一版聚合 SQL 6 月窗口 fee 列有 0.05% 微差,根因是订单跨天被 SUM 多次。修法:fee 查询加 DISTINCT (order_id, category_id, order_fee)。修复后 diff 完全为空。这个坑只有靠 6 月窗口对账才能暴露,1 月窗口看不出来。

最终成绩单

时间窗口 优化前 聚合表 加速 用户体感
1 天228 ms47 ms5x已经够快
1 周271 ms52 ms5x已经够快
1 月1,868 ms122 ms15x等 2 秒 → 秒开
3 月13,013 ms148 ms88x等 13 秒 → 秒开
6 月47,644 ms183 ms260x不能用 → 秒开
全年估 90+ 秒203 ms400x+完全不能用 → 秒开

6 · 上线步骤 + 风险

6 步落地

1
生产建表
执行 build_agg_tables.sql,3 张新表,无对现有表的影响。
2
生产全量回填
执行 backfill_agg.sql。本地耗时 4.5 秒,生产 RDS 估计 < 30 秒。
3
加 cron 任务
*/5 * * * * 跑滑动 7 天重算脚本。可以放在 162 机器上。
4
应用层改 SQL
weberp.xipugold.com 的 ThinkPHP 代码里找到报表 SQL(应在 Model/ 目录),替换为聚合表版本。
5
灰度
老 SQL 保留,新 SQL 用 URL 参数 ?use_agg=1 切换,选几个导购账号试用 1 周。
6
切流
默认走聚合表,观察一周无问题后删除老 SQL 路径。

已知风险

  1. 数据修补 操作员改了 7 天前的单据 → 滑动窗口追不上。 对策: 加一个「全量重建」按钮(本地 4.5 秒搞定),或 cron 每天凌晨跑一次全量。
  2. 新增维度 业务想加新维度(门店分组、SKU 下钻) → 聚合表 schema 需同步改。 对策: 改了应用层 SQL 同时建新聚合表即可。
  3. 品类归属变更 xipunum_erp_ksiamges 改了 SKU 的品类映射 → 历史聚合数据不会自动追溯。 对策: 改完触发一次全量重建即可。这是低频操作。

📁 实施产物