distinct常触发using filesort和using temporary,因其依赖sort-based去重:先全量读取再排序去重,内存不足时写磁盘;建覆盖索引可避免排序,实现顺序扫描即时去重。

为什么DISTINCT常触发Using filesort和Using temporary
DISTINCT本质是去重,数据库必须对结果集做全量比较。MySQL默认走Sort-Based去重:先把所有匹配行读出来,再排序、相邻去重;如果内存不够,就会写磁盘临时文件——这就是执行计划里出现Using filesort和Using temporary的根源。哪怕只查两个字段,只要没索引支撑,照样扫全表+排序。
建覆盖索引让DISTINCT跳过排序
核心思路是让数据库能“顺序扫描+即时去重”,不回表、不排序。关键看查询字段是否被索引完全覆盖:
- 单字段:如
SELECT DISTINCT status FROM orders→ 建INDEX(status) - 多字段:如
SELECT DISTINCT user_id, product_id FROM clicks→ 必须建联合索引INDEX(user_id, product_id),顺序不能颠倒 - 带WHERE条件:如
SELECT DISTINCT a, b FROM t WHERE c = 1→ 建INDEX(c, a, b),c必须为前导列,否则索引失效
用EXPLAIN验证:如果type是index且Extra里没有Using filesort或Using temporary,说明索引生效了。
用GROUP BY替代DISTINCT时要注意语义和索引
在MySQL 5.7+、PostgreSQL等引擎中,GROUP BY和DISTINCT底层优化路径可能不同,尤其当字段有索引时:SELECT a, b FROM t GROUP BY a, b有时比SELECT DISTINCT a, b FROM t更快,因为优化器更倾向用索引驱动的SortAgg而非强制排序。
- 必须确保
GROUP BY字段顺序与联合索引完全一致,否则仍会触发Using filesort -
GROUP BY允许加聚合函数(如MIN(id)),而DISTINCT不能——这反而是优势,比如取每个用户最新记录时,ROW_NUMBER()比先DISTINCT再关联更稳 - 语义风险:
GROUP BY在严格模式下要求SELECT列表所有非聚合字段都出现在GROUP BY中,否则报错
先过滤、再DISTINCT,别让去重扛全量数据
DISTINCT作用的数据集越小,性能越好。常见错误是把过滤逻辑放在DISTINCT之后,或者用SELECT DISTINCT *硬扛:
- 避免
SELECT DISTINCT * FROM orders WHERE dt >= '2024-01-01'→ 改成只选必要字段,如SELECT DISTINCT user_id, status - 分区表场景下,
WHERE dt = '2024-01-01'本身已限定了分区,但若没分区,务必加AND user_id IS NOT NULL过滤空值,减少参与去重的行数 - 高基数场景(如千万级日志中只几百个活跃用户),先用
EXISTS或子查询缩小范围:SELECT DISTINCT user_id FROM events WHERE user_id IN (SELECT user_id FROM active_users),比全表扫快得多
最常被忽略的一点:加索引之前,先跑一遍EXPLAIN。有时候一个INDEX(status, created_at)就能让SELECT DISTINCT status FROM logs WHERE created_at > '2026-01-01'从3秒降到0.02秒,比重写五层嵌套SQL还管用。










