Using temporary或Using filesort是MySQL未走索引分组的确诊标志,表明需全量读取数据硬算;应按WHERE→GROUP BY→SELECT顺序创建联合索引,并避免在GROUP BY中使用函数。

EXPLAIN里出现Using temporary或Using filesort就是病根
这两个提示不是警告,是确诊书:MySQL没走索引分组,被迫把数据全读进内存或磁盘临时表再硬算。哪怕表只有80万行,一见这两个词,查询就大概率从200ms跳到8秒以上。
实操建议:EXPLAIN FORMAT=TRADITIONAL SELECT ... GROUP BY ... 必须先跑;重点盯Extra列,不是key列——哪怕key显示用了索引,Extra里有Using temporary,说明索引根本没帮上分组的忙。
-
type是ALL或index(而非ref/range),基本等于索引失效 -
key_len异常大(比如VARCHAR(255)字段只查前10个字符,但key_len显示765),说明索引定义和实际查询不匹配 - 别只看
WHERE有没有走索引——GROUP BY字段没被覆盖,照样触发临时表
联合索引必须按WHERE → GROUP BY → SELECT顺序建
不是把字段堆进CREATE INDEX就行。MySQL的B+树只在“最左前缀匹配”且能按顺序输出分组键时,才支持松散索引扫描(Loose Index Scan)——这才是毫秒级的关键。
假设语句是:SELECT user_id, COUNT(*) FROM orders WHERE status = 1 AND created_at > '2024-01-01' GROUP BY user_id, product_type;
- 过滤字段
status区分度高,放最左;created_at是范围条件,放中间;分组字段user_id, product_type紧随其后 → 索引应为INDEX(status, created_at, user_id, product_type) - 如果还要
SELECT MAX(amount),把amount加到索引末尾,形成覆盖索引,避免回表 - 千万别写成
INDEX(user_id, product_type, status)——WHERE条件无法利用最左前缀,索引直接废掉
别在GROUP BY里用函数,DATE(created_at)这种写法等于自废武功
函数会让索引完全失效,MySQL只能全表扫描再逐行计算。不是“可能慢”,是“必然慢”,且无法通过调参绕过。
错误写法:GROUP BY DATE(created_at);正确解法分两种场景:
- 高频按天统计:加冗余字段
created_date DATE,建索引INDEX(created_date, user_id),查询改用WHERE created_date BETWEEN ... GROUP BY created_date - 低频灵活查询:用范围条件缩小数据量,例如
WHERE created_at >= '2024-06-01' AND created_at < '2024-07-01' GROUP BY HOUR(created_at),至少先砍掉90%数据再分组 - MySQL 5.7不支持函数索引,别指望
INDEX(DATE(created_at))能生效
千万级以后,物化中间结果比死磕单条SQL更靠谱
当GROUP BY day, city, status这种组合每小时都要查,还要求亚秒响应,索引和SQL_BIG_RESULT只是止痛药。真正扛压靠的是把聚合结果提前算好存起来。
- 用
CREATE TABLE summary_daily AS SELECT DATE(created_at) day, city, status, COUNT(*) cnt FROM orders GROUP BY day, city, status生成汇总表 - 后续增量更新用
INSERT INTO summary_daily ... ON DUPLICATE KEY UPDATE cnt = cnt + VALUES(cnt),主键设为(day, city, status) - 物化表必须建索引:
INDEX(day, city, status),否则查单日数据仍会扫全表 - 别依赖CTE或子查询——MySQL 8.0默认不物化CTE,它会被内联展开,回到嵌套原点;真要复用中间结果,就得落地成表
真正卡住性能的,往往不是语法本身,而是没意识到GROUP BY本质是一次全量重计算——而业务要的通常只是“最新一天的统计”,不是“从建库第一天算到今天”。


















