mysql中SELECT语句中DISTINCT的优化方法

13次阅读

DISTINCT 变慢是因为其本质是隐式 GROUP BY,会触发临时表和文件排序;需用匹配顺序的联合覆盖索引优化,或改写为 GROUP BY 启用松散索引扫描。

mysql中SELECT语句中DISTINCT的优化方法

为什么 DISTINCT 会变慢

mysqlDISTINCT 本质是去重,执行时通常会触发临时表(using temporary)和文件排序(Using filesort),尤其当字段没索引、结果集大或涉及多列组合时,性能下降明显。它不是语法糖,而是隐式 GROUP BY,MySQL 5.7+ 后底层基本等价于 GROUP BY 字段列表。

加索引不一定管用,关键看覆盖和顺序

select DISTINCT col1, col2 加单列索引 (col1)(col2) 通常无效;必须建联合索引且满足最左前缀原则,并尽可能覆盖查询字段:

  • 理想索引:ALTER table t ADD INDEX idx_distinct (col1, col2) —— 能让 MySQL 直接遍历索引树去重,避免回表和临时表
  • 如果还查了 col3(如 SELECT DISTINCT col1, col2, col3),单靠 (col1, col2) 索引仍要回表,此时应考虑覆盖索引:(col1, col2, col3)
  • 注意:索引列顺序必须和 DISTINCT 字段顺序一致,DISTINCT col2, col1 无法利用 (col1, col2) 索引

用 GROUP BY 替代 DISTINCT 常能启用松散索引扫描

在某些场景下,显式写 GROUP BYDISTINCT 更容易触发优化器的松散索引扫描(Loose Index Scan),大幅减少扫描行数。前提是:分组字段有合适索引,且无聚合函数或 HAVING 子句干扰。

SELECT DISTINCT city, district FROM users; -- 可尝试改写为: SELECT city, district FROM users GROUP BY city, district;

两者语义等价,但后者在有 (city, district) 索引时,可能跳过重复值区间,而前者有时仍走紧凑扫描(Tight Index Scan)。

避免 SELECT * + DISTINCT 和大字段参与去重

DISTINCT 对所有选中列做哈希/排序比对,若包含 TEXTBLOB 或长 VARCHAR,不仅内存占用高,还可能触发磁盘临时表(Created_tmp_disk_tables 上升)。常见错误包括:

  • SELECT DISTINCT *, created_at FROM logs —— * 让优化器无法裁剪,且大字段拖慢整个去重流程
  • SELECT DISTINCT CONCAT(name, '-', id) AS key FROM t —— 表达式结果无法走索引,强制全表计算再去重
  • 正确做法:只选必要字段;用主键或唯一标识替代长文本拼接;必要时先子查询过滤再 DISTINCT

真正卡顿的地方往往不在语法本身,而在没意识到 DISTINCT 是“隐式 GROUP BY + 全字段比对”——索引是否覆盖、字段是否可比较、结果集是否被业务逻辑悄悄撑大,这些细节比记住语法更重要。

text=ZqhQzanResources