distinct 本质是隐式 group by,需全量拉取数据建哈希表或排序去重,易引发临时表膨胀、磁盘 io 增加及谓词下推失效;仅当 select 字段完全匹配联合索引最左前缀且 where 条件命中该索引时才可能走索引。

为什么DISTINCT会让查询变慢
DISTINCT 不是过滤器,它本质是隐式 GROUP BY:数据库必须把所有 SELECT 字段的值全拉出来,建哈希表或排序去重。中间结果集可能比原表还大,尤其当字段含 TEXT、JSON 或多列组合时,内存和临时磁盘压力陡增。
常见错误现象:EXPLAIN 里出现 Using temporary 和 Using filesort;响应时间从毫秒跳到秒级;tmp_table_size 被撑爆后写磁盘临时表。
- MySQL 8.0+ 对单表
DISTINCT启用哈希聚合,但一旦混用JOIN、GROUP BY或子查询,立刻退化回排序路径 - PostgreSQL 默认走排序,不自动哈希;SQLite 只对单列
DISTINCT有索引短路优化,多列无效 -
DISTINCT会关闭谓词下推——子查询里加了它,外层WHERE就没法下推,哪怕只取LIMIT 10,也得先把 50 万行全算出来
怎么让 DISTINCT 走索引
能走索引的前提很具体:必须是 SELECT 的所有字段,都出现在同一个联合索引的最左前缀中,且 WHERE 条件也命中该索引。
例如表 orders(user_id, status, created_at),执行 SELECT DISTINCT user_id, status FROM orders 可走索引;但 SELECT DISTINCT status, user_id 就不行(顺序不匹配)。
- 如果加了
WHERE created_at > '2024-01-01',而索引是(user_id, status),那依然用不上——created_at不在索引里 - 正确做法是建覆盖索引:
CREATE INDEX idx_created_user_status ON orders(created_at, user_id, status) - 别指望
ORDER BY和DISTINCT共享索引优化:MySQL 5.7 不支持;8.0+ 仅当ORDER BY字段完全被DISTINCT字段包含时才可能复用
用 GROUP BY 替代 DISTINCT 真的更高效吗
语义等价时,GROUP BY 常比 DISTINCT 更可控——尤其配合索引时,某些引擎(如 ClickHouse)对 GROUP BY 的优化更成熟。
比如 SELECT DISTINCT city, category FROM t,可改写为 SELECT city, category FROM t GROUP BY city, category,再配上索引 (city, category),效果一致但执行计划更稳定。
-
GROUP BY支持后续加聚合函数(如MIN(updated_at)),DISTINCT不能 - 注意 MySQL 的
ONLY_FULL_GROUP_BY模式:若SELECT列没全在GROUP BY中,会报错,不是性能问题而是语义校验 - 别写
SELECT DISTINCT dept_id, COUNT(*) FROM t GROUP BY dept_id——这是逻辑冲突:GROUP BY已保证dept_id唯一,DISTINCT纯属多余,却强制扫两次表
真正该先问的问题:你真的需要 DISTINCT 吗
很多 DISTINCT 是“补丁式写法”:JOIN 导致笛卡尔积,再靠它兜底。这掩盖了数据模型缺陷,代价远高于修复本身。
先检查 JOIN 条件是否完整、关联字段是否有索引;用 EXISTS 替代 JOIN + DISTINCT 查存在性;把去重下沉到 CTE 或子查询阶段,而不是最后一步硬扛。
- 查“买过商品 A 的用户”,别写
SELECT DISTINCT u.id FROM users u JOIN orders o ON u.id = o.user_id JOIN items i ON o.item_id = i.id WHERE i.name = 'A' - 改用
SELECT u.id FROM users u WHERE EXISTS (SELECT 1 FROM orders o JOIN items i ON o.item_id = i.id WHERE o.user_id = u.id AND i.name = 'A') - 如果只是要统计数,
COUNT(DISTINCT)在亿级数据上根本不是调优问题——是架构选择问题:接受 ±1% 误差就用APPROX_COUNT_DISTINCT,否则必须预聚合
DISTINCT,几乎都会触发临时表膨胀或全量物化——这时候索引再好也没用。











