explain显示using temporary + using filesort说明distinct强制创建临时表去重,且order by字段不匹配时额外排序;有索引可避免临时表,否则必走临时表。

EXPLAIN 显示 Using temporary + Using filesort 就代表 DISTINCT 在走临时表
MySQL 对 DISTINCT 的去重不是靠哈希或排序后跳过重复,而是默认先建临时表(Using temporary),再对临时表去重。如果 ORDER BY 字段和 SELECT DISTINCT 字段不一致,还会多出 Using filesort —— 这说明它在临时表里又做了一次排序,性能损耗明显。
常见错误现象:EXPLAIN 看到 type=ALL 且 Extra 含 Using temporary; Using filesort,但 SQL 看起来只是简单去重,比如 SELECT DISTINCT user_id FROM log_table。
- 如果
user_id有索引,MySQL 可能用索引扫描+去重合并,避免临时表;没索引就一定走临时表 -
DISTINCT作用于多列时(如SELECT DISTINCT a,b),等价于GROUP BY a,b,优化器处理逻辑相同 - 5.7+ 版本对单列
DISTINCT会尝试用松散索引扫描(Loose Index Scan)跳过重复值,但前提是该列是复合索引最左前缀且无WHERE条件干扰
用 GROUP BY 替代 DISTINCT 不一定能提速,要看是否带聚合函数
很多人以为把 DISTINCT 换成 GROUP BY 就能“控制”去重逻辑,其实两者执行计划几乎一样——优化器内部会把 DISTINCT 重写为 GROUP BY。真正影响性能的是有没有可用索引、是否需要回表、是否触发临时表。
使用场景:当你发现 DISTINCT 很慢,想手动干预时,可尝试加 GROUP BY 配合 MIN()/MAX() 提取某条代表记录,但这已超出纯去重范畴。
- 纯去重需求下,
SELECT DISTINCT a FROM t和SELECT a FROM t GROUP BY a生成的执行计划完全一致 - 如果加了
ORDER BY且字段不在DISTINCT列中(如SELECT DISTINCT a FROM t ORDER BY b),MySQL 必须先查全量再排序去重,无法利用索引加速 - 8.0.13+ 支持
GROUP BY的SKIP SCAN优化,但仅限于某些复合索引结构,DISTINCT不直接受益
覆盖索引能让 DISTINCT 避开回表,但不能绕过临时表
只要 SELECT DISTINCT 的所有字段都在同一个索引里(即覆盖索引),就能避免从聚簇索引回表读数据行,这是提升速度的关键一环。但它无法消除 Using temporary —— 去重本身仍需内存/磁盘临时结构来判断重复。
参数差异:tmp_table_size 和 max_heap_table_size 共同决定临时表能否在内存中完成;超限就会落盘,性能断崖式下降。
- 例如
SELECT DISTINCT status FROM order_table,若status是独立索引或复合索引最左列,就能走索引扫描 - 但如果写成
SELECT DISTINCT status, created_at FROM order_table,而索引是(status)单列,就不覆盖;必须建(status, created_at)才能覆盖 - 注意:即使用了覆盖索引,
EXPLAIN仍可能显示Using temporary,这不是误报,是去重动作本身所需
去重逻辑实际发生在存储引擎层之上,不是 SELECT 阶段才开始
MySQL 的去重不是在 Server 层逐行比对字符串,而是由执行器边读取边维护一个去重集合(内存哈希表或磁盘临时表)。这意味着:数据越早被过滤掉,去重开销越小;反过来,如果 WHERE 条件写得松散(比如用 LIKE '%abc'),导致大量无关行进入去重流程,效率就极低。
容易踩的坑:在 DISTINCT 查询里嵌套子查询或 JOIN,结果集膨胀后再去重,等于把压力全堆给临时表。
- 优先把过滤条件下沉到最内层,比如把
SELECT DISTINCT u.id FROM user u JOIN log l ON u.id=l.uid WHERE l.time > '2024-01-01'改成先查log表符合条件的uid,再关联user -
DISTINCT对 NULL 值视为相同值去重,这点和GROUP BY一致,但和应用层去重逻辑可能不一致,要注意业务语义 - 如果去重字段类型是
TEXT或长VARCHAR,临时表会更占内存,甚至直接触发磁盘临时表,比字段短小的情况慢数倍
真正难调的不是语法怎么写,而是去重字段是否落在有效索引路径上、WHERE 是否足够收敛、以及临时表大小是否卡在内存临界点附近——这三个地方稍有偏差,执行时间就从毫秒跳到秒级。










