SQL优化案例:从30秒到30毫秒
最近处理了一个典型的SQL性能问题:一个报表查询从30秒优化到30毫秒,性能提升1000倍。整个过程涉及索引优化、SQL改写、执行计划分析等多个环节,非常有代表性。分享出来供大家参考。
问题背景
运营后台的一个"月度销售统计"页面,打开需要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. 优化要量化——每次优化前后都要对比执行时间和扫描行数。
