前言
开发过程中经常面临数据去重需求,大家常会纠结两种方案:
- 使用
distinct在单条结果集中完成去重; - 使用
union合并多条查询并自动去重。
还有很多开发者分不清 union 和 union all 的巨大差异,经常误用导致数据库出现不必要的性能消耗。
本文对比两者原理、适用场景、性能差距,给出线上环境选型标准。
一、先理清基础语法与核心行为
1. distinct
作用:对单条sql的结果集进行去重。
select distinct user_id from `user_login_log` where `date` = curdate();
执行逻辑:数据库取出所有满足条件的数据,按照指定字段进行分组对比,剔除重复行,保留唯一记录。
2. union 与 union all(重点区分)
-- 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 + 排序操作
二、底层实现原理对比
distinct 原理
在结果集内部构建临时内存哈希表或者排序缓冲区,遍历数据消除本行内重复记录。
数据量较小使用内存;数据量大超过缓冲区限制,则落地磁盘临时文件,性能断崖下跌。
union 原理
- 分别执行前后两条子查询;
- 使用
union all把所有数据纵向汇总; - 全局执行一次distinct排序去重。
简单公式:
union = union all + distinct
三、核心性能结论
- 如果业务需要合并多条sql结果并且去重:可以使用
union; - 如果多条sql合并,原始数据不存在重复,优先使用
union all,不要用 union; - 如果只是单表/单条查询内部去重,不要使用 union,直接使用
distinct; - 杜绝滥用 union 实现单条sql内部去重,属于完全错误用法。
四、场景分类实战分析
场景1:单条查询内部去除重复数据
✅ 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会执行两次相同查询,扫描双倍数据,额外执行全局去重,资源翻倍浪费。
场景2:多条独立查询结果合并,需要全局去重
需求:从用户表、密钥表两处查询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隐性排序带来开销。
场景3:多条查询合并,明确不存在重复数据
直接使用 union all,不要使用union,不要额外加distinct
省去全局比较、排序、去重的巨大开销。
select id from `user` limit 100 union all select id from `app_key` limit 100;
五、高频误区汇总
误区1:union 和 distinct 可以随意互相替换
不能替换。
distinct作用于单查询内部;
union作用于多条查询合并之后,适用场景边界完全不同。
误区2:union去重性能优于 union all + distinct
恰恰相反。
union强制执行排序去重;union all只做拼接,把去重选择权交给外层,优化器拥有更多优化策略。
误区3:少量数据,随便写无所谓
在测试环境少量数据看不出差距;当结果集上万、十万级别,union额外排序会直接引发慢查询,线上极易爆出性能故障。
误区4:不知道union自带排序,导致不必要的消耗
mysql union规范:合并完成后会执行排序操作;如果你不需要排序,不要使用union。
六、索引层面额外优化提示
distinct查询尽量建立覆盖索引,避免大量回表;
-- 示例:利用索引直接完成去重,无需读取原始数据表 create index idx_date_user on user_login_log(`date`,user_id);
union all拆分多条查询时,每条子查询务必保证可以正常命中索引;
如果最终只需要获取第一条匹配记录,可以每层子查询增加 limit 实现短路查询,减少扫描行数。
七、选型决策清单(线上直接套用)
- 仅单条sql内部去重 → 使用
distinct - 多条sql结果合并,存在重复且需要去重 → 优先:
union all + 外层distinct - 多条sql结果合并,确认无重复 → 使用
union all - 禁止:单条查询场景强行拆分使用union做去重
- 禁止:能用union all的场景随意使用union
八、验证手段
使用 explain 观察执行计划:
- union:通常能看到
using temporary; using filesort(临时表+文件排序) - union all:没有全局排序与临时表,执行计划更加简洁
总结一句话:去重工具没有绝对好坏,分清场景再选择;能使用 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,性能更好,也是线上检索场景标准写法。
到此这篇关于浅析mysql查询去重是使用union还是distinct的文章就介绍到这了,更多相关mysql查询去重内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论