distinct不是解法而是掩盖join缺陷的补丁,它在膨胀后的中间结果上强行去重,导致性能恶化;真正应优化join逻辑、索引和查询结构。

DISTINCT 是补丁,不是解法
加 DISTINCT 后查询“不重复”了,但性能反而更差——这不是 DISTINCT 本身慢,而是它把 JOIN 逻辑缺陷(比如漏写关联条件、缺少索引、一对多膨胀)给盖住了。数据库得先拼出几万行中间结果,再花时间去重,而问题根源其实在 JOIN 阶段就该被掐断。
JOIN 膨胀后去重,开销翻倍
一对多 JOIN(如一个用户对应多个订单)会让主表记录被“撑开”。DISTINCT 不是按你想要的字段去重,而是对整行去重——哪怕只选 user_id 和 name,只要 JOIN 后某列(比如 order_amount)不同,就被算作不同行。结果就是:1000 个用户 × 平均 5 条订单 = 5000 行中间结果,DISTINCT 得对这 5000 行做哈希或排序。
- 用
EXPLAIN看执行计划,如果出现Using temporary或Using filesort,基本就是DISTINCT在硬扛膨胀数据 -
SELECT DISTINCT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id→ 实际去重的是连接后的 5000 行,不是 1000 个用户 - 真正该做的,是确认“我到底要不要订单数据”:要,就用
GROUP BY或窗口函数;不要,就别JOIN,改用EXISTS
掩盖索引失效和驱动表错配
当 DISTINCT 被加在最后,优化器容易放弃下推过滤条件。比如 WHERE 中本可提前过滤的 o.status = 'paid',可能被拖到 JOIN 完成后才执行,导致全量 JOIN 再裁剪。更隐蔽的是:驱动表选错 + 缺少 user_id 索引时,JOIN 本身已在全表扫描,DISTINCT 只是让这个错误“看起来有结果”。
- 检查
EXPLAIN的type列:如果是ALL或index(非覆盖),说明 JOIN 没走索引 -
orders表上必须有(user_id)单列索引,或(user_id, status)复合索引,否则EXISTS子查询也会循环扫全表 -
ON条件里混写过滤(如ON o.user_id = u.id AND o.status = 'paid')比WHERE更早生效,但要注意语义是否等价
替代方案选错,问题照旧
有人把 DISTINCT 换成 GROUP BY 就以为优化完了,结果发现更慢。因为 GROUP BY 在没索引时同样触发临时表和排序,而且字段顺序不对、没覆盖索引,照样白搭。
-
SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name—— 这比DISTINCT好一点,但依然依赖 JOIN 结果集大小 - 真正轻量的写法是:
SELECT u.id, u.name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid') - 如果真要带子表信息(如最新订单时间),用
ROW_NUMBER()窗口函数,而不是靠DISTINCT碰运气
SELECT * 看原始 JOIN 结果,数一数一行用户变了几行——那才是你该优化的起点,不是 DISTINCT 放哪儿。











