应优先优化Query_time × Rows_examined乘积大的聚合语句,结合mysqldumpslow分析、EXPLAIN字段(type、rows、Extra、key)及覆盖索引设计,并在高频低变场景改用带幂等校验的汇总表。

怎么从慢查询日志里揪出最耗时的聚合语句
直接看 Query_time × Rows_examined 的乘积,不是单看执行时间。一条 SUM() 聚合查了 500 万行、平均耗时 1.2 秒,比一条 COUNT(*) 扫了 8000 万行、平均耗时 0.8 秒更值得先动——因为前者总扫描量小但频次高,后者单次开销大且容易压垮连接池。
用 mysqldumpslow -s t -t 10 /path/to/slow.log 按总耗时排序;如果日志里有 SELECT SUM(、AVG(、GROUP BY 关键字,基本可以锁定目标。注意过滤掉带 /*+ NO_INDEX */ 这类 hint 的语句,它们往往是临时绕过索引的调试行为,不是真实瓶颈。
- 别只信
Query_time,要结合Rows_examined判断扫描效率 - 聚合语句常伴随
Using temporary; Using filesort,在Extra字段里一搜就出来 - 如果
Rows_sent很小(比如 1 或几十),但Rows_examined上百万,说明过滤弱、聚合重,大概率能优化
EXPLAIN 里哪些字段暴露聚合性能问题
type = ALL 或 type = index 是最危险信号,尤其当 key 是 NULL 时,说明连基础索引都没走。聚合本身不直接触发索引失效,但 WHERE 条件写法会——比如 WHERE DATE(create_time) = '2026-09-01' 就会让 create_time 上的索引完全作废。
重点盯这三个地方:
-
rows:预估扫描行数,超过表总行数 20% 就该怀疑是否漏了条件或索引错配 -
Extra出现Using temporary:MySQL 不得不建内存临时表存中间分组结果,数据量一大就卡 -
key显示用了索引但rows依然巨大:可能是联合索引顺序不对,比如查询用WHERE status = 1 AND create_time > '2026-09-01',但索引是(create_time, status),那status就无法高效过滤
聚合字段上建索引到底有没有用
单独给 SUM(amount) 建索引没意义,但建覆盖索引有用。例如查询是 SELECT user_id, SUM(amount) FROM orders WHERE create_time BETWEEN ? AND ? GROUP BY user_id,那么索引 INDEX idx_time_user_amount (create_time, user_id, amount) 可以让 MySQL 完全避开主表,只扫索引树就完成过滤 + 分组 + 聚合。
注意两个硬限制:
- MySQL 5.7+ 才支持在二级索引里直接做
SUM/COUNT聚合(依赖索引列包含所有需要字段) -
amount是NULL容忍型字段时,SUM会跳过它,但索引仍需包含该列才能避免回表 - 别在
TEXT、BLOB字段上建索引,也别指望对它们做高效聚合
什么时候该放弃实时聚合,改用汇总表
当发现某条 GROUP BY 查询每天被调用上千次、且结果变化频率低于 1 小时,就该切到预计算。比如每小时跑一次:INSERT INTO order_daily_summary (date, user_id, total_amount, order_count) SELECT CURDATE(), user_id, SUM(amount), COUNT(*) FROM orders WHERE DATE(create_time) = CURDATE() GROUP BY user_id,后续业务查汇总表即可。
这种方案的坑比想象中多:
- 定时任务失败会导致数据断层,必须加幂等写入和校验逻辑
- 如果查询带动态时间范围(如“最近 7 天”),汇总表得按天粒度存,再用
SUM()在汇总表上二次聚合 - 不要用触发器自动维护汇总表——高并发写入下极易锁表或延迟累积
真正难的从来不是写一条更快的 SUM,而是判断这条 SUM 是否本就不该每次都在数据库里算。










