mysql执行计划中的Distinct操作如何去重_mysql去重算法解析

6次阅读

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

mysql执行计划中的Distinct操作如何去重_mysql去重算法解析

EXPLAIN 显示 Using temporary + Using filesort 就代表 DISTINCT 在走临时表

mysqlDISTINCT 的去重不是靠哈希或排序后跳过重复,而是默认先建临时表(Using temporary),再对临时表去重。如果 ORDER BY 字段和 select DISTINCT 字段不一致,还会多出 Using filesort —— 这说明它在临时表里又做了一次排序,性能损耗明显。

常见错误现象:EXPLAIN 看到 type=ALLExtraUsing 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 tSELECT a FROM t GROUP BY a 生成的执行计划完全一致
  • 如果加了 ORDER BY 且字段不在 DISTINCT 列中(如 SELECT DISTINCT a FROM t ORDER BY b),MySQL 必须先查全量再排序去重,无法利用索引加速
  • 8.0.13+ 支持 GROUP BYSKIP SCAN 优化,但仅限于某些复合索引结构,DISTINCT 不直接受益

覆盖索引能让 DISTINCT 避开回表,但不能绕过临时表

只要 SELECT DISTINCT 的所有字段都在同一个索引里(即覆盖索引),就能避免从聚簇索引回表读数据行,这是提升速度的关键一环。但它无法消除 Using temporary —— 去重本身仍需内存/磁盘临时结构来判断重复。

参数差异:tmp_table_sizemax_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
  • DISTINCTNULL 值视为相同值去重,这点和 GROUP BY 一致,但和应用层去重逻辑可能不一致,要注意业务语义
  • 如果去重字段类型是 TEXT 或长 VARCHAR,临时表会更占内存,甚至直接触发磁盘临时表,比字段短小的情况慢数倍

真正难调的不是语法怎么写,而是去重字段是否落在有效索引路径上、WHERE 是否足够收敛、以及临时表大小是否卡在内存临界点附近——这三个地方稍有偏差,执行时间就从毫秒跳到秒级。

text=ZqhQzanResources