distinct多次出现通常暴露join关系混乱或建模缺陷:主从表语义边界模糊、一对多误作一对一、字段混选致去重失控;应优先通过子查询/窗口函数拆分维度、修复脏数据源头、避免视图滥用,并用索引优化替代盲目去重。

DISTINCT 多次出现,大概率说明 JOIN 关系没理清
当你发现一个查询里反复出现 DISTINCT,尤其是跨多表 JOIN 后才加它,基本可以判断:主表和从表的语义边界模糊了。比如查“用户+最近一笔订单金额”,却写成 SELECT DISTINCT u.id, u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id——这其实暴露了两个问题:一是没明确“最近一笔”这个业务逻辑该由谁负责(是 JOIN 还是子查询?),二是把本该在应用层或聚合层处理的逻辑,硬塞进连接层。
- 一对多关系被当成了“一对一”来用:JOIN 本身会把一个用户撑开成 N 行,
DISTINCT只是强行压回去,但压不掉中间膨胀的数据量 - 字段混选导致去重粒度失控:选了
u.id, u.name, o.amount, o.created_at,哪怕只想要用户 ID 和名字,DISTINCT也得比对全部四列——只要时间戳毫秒级不同,就不是“重复” - 真正该做的,是把“用户维度”和“订单维度”拆开:先用子查询或窗口函数拿到每个用户的最新订单,再和用户表关联,而不是靠
DISTINCT拼凑结果
查出重复,先别加 DISTINCT,先看 ER 图里有没有缺失的约束
如果 SELECT DISTINCT a, b FROM t 返回行数远少于 SELECT COUNT(*) FROM t,别急着保留这个写法。先检查这张表是否本该有唯一约束:a 和 b 的组合是否构成业务意义上的主键?比如订单明细表里 order_id + product_id 应该唯一,但实际存在重复,说明要么 ETL 写入逻辑有 bug,要么建表时漏了 UNIQUE INDEX (order_id, product_id)。
- 用
SELECT a, b, COUNT(*) FROM t GROUP BY a, b HAVING COUNT(*) > 1快速定位重复源头 - 如果重复是脏数据,修复应在写入侧(触发器、应用校验、ETL 清洗),而不是每次查询都靠
DISTINCT掩盖 - 如果重复是合理业务现象(如同一用户多次提交相同配置),那
DISTINCT就不该出现在查询里——你真正需要的是去重后的最新时间、或计数、或状态聚合,而不是“随便留一条”
DISTINCT 出现在视图定义里,等于给所有下游查询埋雷
视图一旦定义含 DISTINCT,所有调用它的 SQL 都无法绕过这个操作。更麻烦的是,优化器往往无法把外部 WHERE 条件下推到 DISTINCT 之前执行,导致先全量去重、再过滤。比如视图 v_user_orders 定义为 SELECT DISTINCT u.id, u.name, o.status FROM users u JOIN orders o ...,而你只想要 status = 'paid' 的用户,数据库仍可能扫完所有订单再筛。
- EXPLAIN 时重点看
type是否为ALL或index,key是否为空——这是DISTINCT让索引失效的典型信号 - 替代方案不是换
GROUP BY,而是删掉视图里的DISTINCT,改用物化汇总表或带索引的子查询 - 如果必须保留去重语义,优先建覆盖索引,例如视图查
DISTINCT status, category,就建INDEX(status, category),且确保查询条件能命中该索引最左前缀
真正难的不是写对 DISTINCT,而是判断它出现的位置是否暴露了建模漏洞——比如本该用外键约束的地方用了应用层校验,本该用聚合函数的地方用了连接后去重,本该用唯一索引的地方靠人工定期清洗。这些地方不动,DISTINCT 就永远是个临时胶带,越贴越厚,越贴越容易断。











