distinct本质是整行去重,非单字段筛选;仅当select所有列值完全相同时才合并,null视为相同;有索引时走松散索引扫描,无索引则用临时表,性能差异显著。

DISTINCT 本质是整行去重,不是按单列“挑一个”
很多人误以为 DISTINCT name 是从重复的 name 中随便选一条返回。其实不是:MySQL 把 SELECT DISTINCT name 看作只查一列的结果集,然后对这一列的每一行值做唯一性判断——NULL 算一个独立值,'Alice' 出现三次也只留一次。但如果是 SELECT DISTINCT name, city,那就得把 (name, city) 当成一个元组整体比较,('Alice', 'NYC') 和 ('Alice', 'LA') 完全不同,都会保留。
MySQL 实际用哪套算法,取决于有没有索引
- 没索引时走
Using temporary:全表扫描 → 所有目标字段值塞进内存哈希表(或磁盘临时表)→ 遍历去重 → 返回。数据量大时极易触发磁盘写,慢得明显 - 有索引时可能走松散索引扫描(Loose Index Scan):直接顺序遍历 B+ 树叶子节点,相同值天然相邻,遇到下一个不同值才取一次。不建临时表,也不排序,I/O 和 CPU 都省得多
- 低基数字段(比如 status 只有 3 个值)即使没索引,优化器也可能用特殊路径加速,但不可依赖
EXPLAIN 是唯一能确认执行方式的手段
别猜,直接看 EXPLAIN SELECT DISTINCT user_id FROM orders 的输出:
- 如果
Extra列含Using temporary,说明正在用临时表,性能风险高 - 如果
type是index或range,且key显示用了某个索引,大概率走了索引去重 -
rows值接近总行数?那基本就是全表扫了,哪怕加了索引,也可能因为查询条件或字段类型(如前缀索引太短)导致失效
多字段 DISTINCT 的陷阱比想象中多
-
SELECT DISTINCT a, b FROM t和SELECT a, b FROM t GROUP BY a, b语义等价,但执行计划未必一样;GROUP BY 在某些版本里更倾向用索引,DISTINCT 更容易退化 - 不能在
DISTINCT后混用非去重字段又不加聚合:比如SELECT DISTINCT name, score FROM students ORDER BY score DESC会报错,因为score没参与去重逻辑,MySQL 不知道该按哪个score排 - TEXT/BLOB 类型字段不能直接建完整索引,必须用前缀索引(如
ADD INDEX idx_text(content(50))),而 DISTINCT 要靠这个索引来加速时,前缀长度不够就会失效
真正影响 DISTINCT 性能的,从来不是语法本身,而是你是否盯着 EXPLAIN 里的 Using temporary 这一行,并愿意为高频去重字段补上合适的索引——尤其是当那个字段还常出现在 WHERE 条件里时。











