explain出现using temporary说明mysql已放弃索引短路,强制创建内存或磁盘临时表完成distinct去重;其前提是select字段必须严格按顺序构成联合索引最左前缀且where条件命中该索引前导列,否则必触发全量扫描与临时表落盘。

EXPLAIN里出现Using temporary就说明DISTINCT正在建临时表
MySQL对DISTINCT的处理不是“边扫边去重”,而是先拉全量数据,再统一去重。只要EXPLAIN的Extra列出现Using temporary,就代表它已放弃索引短路,转而创建内存或磁盘临时表——这不是警告,是执行计划已锁定的行为。
常见现象包括:响应时间从毫秒跳到秒级、Handler_read_rnd_next飙升、tmpdir下出现#sql_*临时文件。尤其当结果集超过tmp_table_size(默认16MB)时,临时表立刻落盘,I/O开销断崖式上升。
DISTINCT能走索引的条件非常苛刻
它不是“有索引就行”,而是要求:SELECT DISTINCT的所有字段,必须严格按顺序构成某个联合索引的最左前缀,且WHERE条件也得命中该索引的前导列。
-
SELECT DISTINCT user_id, status FROM orders WHERE status = 'paid'→ 若索引是(status, user_id),可跳过临时表 -
SELECT DISTINCT status, user_id FROM orders→ 同样索引(status, user_id),但字段顺序不匹配,仍走临时表 -
SELECT DISTINCT user_id FROM orders WHERE created_at > '2024-01-01'→ 即使user_id有索引,created_at不在索引里,优化器无法下推条件,大概率全表扫描+临时表
多列DISTINCT或加函数会直接关闭索引路径
一旦涉及多列组合或任意函数包裹,MySQL基本放弃索引去重,强制走哈希/排序路径。
-
DISTINCT UPPER(name):函数导致索引失效,必建临时表 -
DISTINCT name, email, phone:三列必须同时出现在同一联合索引中,且顺序一致;少一列或错序,就退化 -
SELECT DISTINCT a, b FROM t ORDER BY c:ORDER BY字段和DISTINCT无关,MySQL必须先去重再排序,双重压力
GROUP BY通常比DISTINCT更可控,但别混用
语义等价时,GROUP BY在索引利用上往往更稳定,尤其配合聚合函数时。
- 把
SELECT DISTINCT city, category FROM t改成SELECT city, category FROM t GROUP BY city, category,再建索引(city, category),执行计划更容易避开Using temporary - 绝对不要写
SELECT DISTINCT dept_id, COUNT(*) FROM t GROUP BY dept_id:逻辑冲突,GROUP BY已保证dept_id唯一,DISTINCT纯属冗余,触发双重临时表 - MySQL 8.0+虽支持哈希聚合,但一旦混用
JOIN或子查询,立刻退化回排序路径











