MySQL去重时,DISTINCT用于单条查询内部去重,UNION用于合并多条查询并自动去重。UNION自带排序和去重,性能较差,推荐使用UNIONALL加外层DISTINCT替代,避免隐性排序开销。单条查询内去重应直接使用DISTINCT,杜绝滥用UNION。
在MySQL开发中,数据去重是常见的需求,开发者常纠结于两种方案:

长期稳定更新的攒劲资源: >>>点此立即查看<<<
DISTINCT 在单条结果集里直接去重;UNION 合并多条查询并自动去重。很多开发者对 UNION 和 UNION ALL 的区别理解不到位,经常误用,导致数据库白白消耗性能。
这篇文章会从MySQL去重原理、适用场景、性能差距几个维度对比,最后给出线上环境选型的标准做法。
作用:对单条SQL的结果集进行去重。
SELECT DISTINCT user_id FROM `user_login_log` WHERE `date` = CURDATE();
执行逻辑:MySQL数据库取出所有满足条件的数据,按指定字段分组对比,剔除重复行,只保留唯一记录。
-- UNION:合并结果 + 自动去重 + 排序 SELECT user_id FROM `user` WHERE status = 1 UNION SELECT user_id FROM `app_key` WHERE status = 1; -- UNION ALL:仅简单纵向拼接,**不去重、不排序** SELECT user_id FROM `user` WHERE status = 1 UNION ALL SELECT user_id FROM `app_key` WHERE status = 1;
很多人踩过坑:以为 UNION 就是 UNION ALL,其实两者性能差距极大。
UNION = UNION ALL + DISTINCT + 排序操作
在结果集内部构建临时内存哈希表或者排序缓冲区,遍历数据,把本行内的重复记录剔除掉。
数据量小的时候用内存处理;数据量一大,超过缓冲区限制,就会落到磁盘临时文件上,性能断崖式下跌。
UNION ALL 把所有数据纵向汇总;简单公式:
UNION = UNION ALL + DISTINCT
UNION;UNION ALL,别用 UNION;DISTINCT;DISTINCT
需求:查询当日登录日志里所有活跃用户ID,同一用户多条登录记录只展示一次。
-- 正确写法 SELECT DISTINCT user_id FROM user_login_log WHERE `date` = CURDATE(); -- 错误示范,没必要强行拆分UNION SELECT user_id FROM user_login_log WHERE `date` = CURDATE() UNION SELECT user_id FROM user_login_log WHERE `date` = CURDATE();
强行用UNION会执行两次相同查询,扫描双倍数据,还要额外做全局去重,资源翻倍浪费。
需求:从用户表、密钥表两处查询user_id,合并结果,同一个user_id只保留一条。
方案A:UNION
SELECT user_id FROM `user` WHERE username = 'demo' UNION SELECT user_id FROM `app_key` WHERE access_key = 'demo_key';
方案B:UNION ALL + 外层DISTINCT
SELECT DISTINCT user_id FROM (
SELECT user_id FROM `user` WHERE username = 'demo'
UNION ALL
SELECT user_id FROM `app_key` WHERE access_key = 'demo_key'
) t;
重点:方案A 和方案B哪个更快?
绝大多数情况下:UNION ALL + 外层DISTINCT 性能 ≥ UNION
原因:
UNION默认会附带排序行为;
而外层DISTINCT优化器可以选择哈希去重,不一定强制排序,优化空间更大。
追求稳定高性能,推荐统一使用 UNION ALL + DISTINCT 写法,避免UNION隐性排序带来的开销。
直接使用 UNION ALL,不要用UNION,也不要额外加DISTINCT。
省去全局比较、排序、去重的巨大开销。
SELECT id FROM `user` LIMIT 100 UNION ALL SELECT id FROM `app_key` LIMIT 100;
不能替换。
DISTINCT作用于单查询内部;
UNION作用于多条查询合并之后,适用场景边界完全不同。
恰恰相反。
UNION强制执行排序去重;UNION ALL只做拼接,把去重选择权交给外层,优化器拥有更多优化策略。
在测试环境少量数据看不出差距;当结果集上万、十万级别,UNION额外排序会直接引发慢查询,线上极易爆出性能故障。
MySQL UNION规范:合并完成后会执行排序操作;如果你不需要排序,就不要用UNION。
DISTINCT查询尽量建立覆盖索引,避免大量回表;
-- 示例:利用索引直接完成去重,无需读取原始数据表 CREATE INDEX idx_date_user ON user_login_log(`date`,user_id);
UNION ALL拆分多条查询时,每条子查询务必保证能正常命中索引;
如果最终只需要获取第一条匹配记录,可以每层子查询增加 LIMIT 实现短路查询,减少扫描行数。
DISTINCTUNION ALL + 外层DISTINCTUNION ALL使用 EXPLAIN 观察执行计划:
Using temporary; Using filesort(临时表+文件排序)总结一句话:去重工具没有绝对好坏,分清场景再选择;能用 UNION ALL 就不要用 UNION,能避免全局排序就尽量避免。
根据账号、邮箱、密钥多条件检索用户ID:
-- 最优写法
SELECT DISTINCT user_id FROM (
SELECT user_id FROM `user` WHERE username = 'demo'
UNION ALL
SELECT user_id FROM `user` WHERE email = 'demo@test.com'
UNION ALL
SELECT user_id FROM `app_key` WHERE access_key = 'demo_key'
) tmp;
相比直接写三条UNION,性能更好,这也是线上检索场景的标准写法。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述