SQL优化案例:从30秒到30毫秒

陈

陈DevOps

· 阅读 1448

分享
SQL优化案例

SQL优化案例:从30秒到30毫秒

最近处理了一个典型的SQL性能问题:一个报表查询从30秒优化到30毫秒,性能提升1000倍。整个过程涉及索引优化、SQL改写、执行计划分析等多个环节,非常有代表性。分享出来供大家参考。

数据层的稳定性,往往决定了一个系统的下限。「SQL优化案例:从30秒到30毫秒」所涉及的MySQL 与 RDS 性能治理,正是最容易「平时看不见、出事要人命」的部分:平时跑得平稳,一旦出问题就是数据丢失或业务停摆,因此值得投入系统性的精力去治理。

SQL优化案例:从30秒到30毫秒

问题背景

运营后台的一个"月度销售统计"页面,打开需要30秒以上。SQL如下:

SELECT DATE_FORMAT(o.create_time, '%Y-%m') as month, p.category_name, SUM(o.quantity * o.unit_price) as total_amount FROM orders o JOIN products p ON o.product_id = p.id WHERE o.create_time BETWEEN '2023-01-01' AND '2024-12-31' AND o.status = 'completed' GROUP BY month, p.category_name ORDER BY month DESC, total_amount DESC;

第一步:分析执行计划

EXPLAIN显示:orders表全表扫描(type=ALL,rows=1200万),Using temporary; Using filesort。products表走主键索引。问题很明显:orders表缺少合适的索引。

第二步:添加索引

创建联合索引 (status, create_time, product_id, quantity, unit_price)。这是一个覆盖索引——查询所需的所有字段都在索引中,无需回表。添加索引后,执行计划变为range扫描(type=range,rows=80万),Using index。查询时间从30秒降到3秒。

第三步:SQL改写

3秒还是太慢。分析发现GROUP BY DATE_FORMAT()导致MySQL需要对每行数据做函数计算。改写方案:将DATE_FORMAT移到应用层处理,SQL只返回原始日期。同时使用子查询先过滤数据再JOIN,减少JOIN的数据量。

改写后SQL:SELECT o.create_time, o.product_id, o.quantity, o.unit_price FROM orders o WHERE o.status = 'completed' AND o.create_time BETWEEN '2023-01-01' AND '2024-12-31',然后在应用层做JOIN、聚合和格式化。查询时间降到0.5秒。

第四步:引入汇总表

对于这种固定维度的月度统计,最好的方案是预计算。创建月度汇总表 monthly_sales_report,通过定时任务每小时更新。报表页面直接查询汇总表,不再实时聚合。查询时间降到30毫秒。

经验总结

1. 先EXPLAIN定位问题,不要盲目优化。2. 索引优化是性价比最高的手段。3. SQL改写有时比加索引更有效。4. 对于报表类查询,预计算(汇总表/物化视图)是终极方案。5. 优化要量化——每次优化前后都要对比执行时间和扫描行数。

MySQL 与 RDS 性能治理的三个基本盘

其一是可恢复性:备份的价值不在于「做了」,而在于「能恢复」,定期演练恢复流程比增加备份份数更重要。其二是可回滚性:任何结构或参数变更都应有回退路径,避免把生产当试验场。其三是可预估性:容量与增长趋势要有数据支撑,而不是等到告警才响应。

这三个基本盘稳固之后,再去做性能调优和架构升级,风险会小很多。

从排查到预防:把经验沉淀为机制

  • 慢的问题先定位再动手:用执行计划与监控数据说话,避免凭感觉改索引、改参数。
  • 变更留痕:每一次调整记录原因与预期,便于复盘与回退。
  • 容量预警前置:为存储、连接数、内存等关键水位设置提前量告警。
  • 演练常态化:把恢复演练、切换演练纳入日常,而不是出事才第一次做。

机制化的沉淀,能让MySQL 与 RDS 性能治理从「个人经验」变成「团队能力」。