distinct慢的根本原因是全量去重而非索引缺失,需先拉取所有匹配行再排序或哈希去重;替代方案包括group by、exists半连接、窗口函数,而非依赖索引或物化视图。

SELECT DISTINCT 会强制去重计算,不是加索引就能解决的
带 DISTINCT 的视图慢,根本原因不是“没走索引”,而是数据库必须把所有满足条件的行先拉出来,再做全量去重(通常走临时表 + 排序或哈希)。哪怕最终只返回10行,中间可能已扫描并暂存了百万行。这和 WHERE 或 JOIN 阶段能靠索引提前过滤有本质区别。
常见错误现象:EXPLAIN 显示 Using temporary; Using filesort,或者执行计划里出现 Materialize 步骤;实际耗时集中在“去重阶段”,而非“读数据阶段”。
- 如果原表本身字段组合天然不重复(比如主键+固定关联字段),
DISTINCT就是冗余开销 -
DISTINCT作用在多个字段上时,去重成本呈指数级上升(尤其含 TEXT/BLOB 类型) - 视图定义中嵌套了子查询或 JOIN 后再用
DISTINCT,会导致去重发生在宽表结果集上,放大中间数据量
视图里用 DISTINCT 时,索引基本无效
普通查询中,索引能加速 WHERE 过滤或 ORDER BY 排序;但 DISTINCT 的去重逻辑无法被 B-Tree 索引直接支持——索引不存储“是否重复”的元信息,数据库仍需取出所有候选行才能判断。
即使你为 DISTINCT 涉及的所有字段建了联合索引,MySQL/PostgreSQL 也仅可能用它避免回表或优化排序,但不会跳过去重步骤。SQL Server 的列存储索引例外,但普通 OLTP 场景极少使用。
- 联合索引
idx_a_b_c对SELECT DISTINCT a, b FROM t可能减少排序开销,但对SELECT DISTINCT b, a就无效(顺序不匹配) - 在视图定义中建索引毫无意义:视图本身不存数据,索引只能建在基表上
- 试图用覆盖索引“骗过”去重(如
SELECT DISTINCT a FROM t WHERE b=1加idx_b_a)最多省掉回表,去重动作照旧
替代 DISTINCT 的三种实操路径
真正提速,得从源头消除重复产生的逻辑,而不是优化去重过程本身。
- 用
GROUP BY替代(当语义等价时):例如SELECT DISTINCT user_id FROM orders→SELECT user_id FROM orders GROUP BY user_id。部分引擎对GROUP BY有更激进的优化(如 MySQL 8.0+ 的 Loose Index Scan) - 改写为半连接(
EXISTS):比如查“下过单的城市”,不用SELECT DISTINCT city FROM users u JOIN orders o ON u.id=o.user_id,而用SELECT city FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id) - 业务层保证唯一性:如果视图输出本该唯一(如“每个用户最新订单时间”),就别用
DISTINCT,改用窗口函数ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC)过滤出 Top 1
视图定义中 DISTINCT 和物化视图的误区
有人以为把带 DISTINCT 的视图设为物化(如 PostgreSQL 的 MATERIALIZED VIEW)就能一劳永逸,其实风险更大:刷新时照样要全量重算去重逻辑,且锁表时间更长;若基表更新频繁,物化视图很快过期,反而引入一致性问题。
真正适合物化的,是聚合结果稳定、更新频率低的场景(如日级统计报表),而不是靠 DISTINCT “修”数据模型缺陷的视图。
最常被忽略的一点:DISTINCT 往往暴露了 JOIN 方式或数据建模的问题——比如一对多关系没控制好粒度,导致一条主记录被炸成多行。这时候该重构查询逻辑或补约束,而不是给视图贴去重膏药。










