在 MySQL 日常开发中,去重需求极为常见,DISTINCT 和 GROUP BY 是实现去重的两大核心手段。不少开发者对两者的效率差异、适用场景及底层逻辑存在困惑——为何有时两者效率一致,有时却差距悬殊?MySQL 8.0 版本的更新又带来了哪些影响?本文将从原理、性能、版本差异三方面,彻底讲清两者的区别与用法。
核心结论:效率对比的关键变量
DISTINCT 与 GROUP BY 的效率差异,核心取决于是否有索引以及MySQL 版本,核心结论如下:
- 核心结论1:语义相同且存在对应索引时,执行效率基本一致,两者均能高效利用索引;
- 核心结论2:语义相同但无索引时,
DISTINCT 效率高于 GROUP BY(仅针对 MySQL 8.0 之前版本);
- 核心结论3:MySQL 8.0 及之后移除
GROUP BY 隐式排序,无索引场景下两者效率差距大幅缩小。
这里的“语义相同”,特指两者均用于“单纯去重”(无聚合函数)的场景。若涉及 SUM、AVG 等聚合操作,仅能使用 GROUP BY,无对比意义。
底层机制拆解
2.1 DISTINCT:简单直接的去重逻辑
DISTINCT 的核心作用是移除结果集中的重复记录,底层处理逻辑简洁高效,具体规则如下:
- NULL值处理:MySQL 将所有 NULL 值视为相同内容,使用
DISTINCT 去重时,仅保留 1 条 NULL 记录,其余 NULL 值会被过滤。
- 单列去重:对指定单列遍历,直接剔除重复值,无需额外排序操作(无索引时也仅需一次全表扫描)。
- 多列去重:以指定的所有列为联合判断依据,仅当所有列的值完全一致时,才判定为重复记录并剔除。
实操示例:
-- 单列去重,仅保留1条NULL记录
SELECT DISTINCT name FROM user;
-- 多列去重,仅当name和age完全相同时才去重
SELECT DISTINCT name, age FROM user;
2.2 GROUP BY:分组聚合的底层逻辑
GROUP BY 的核心是“分组”,本质是先按指定字段对数据分组,再对每组执行聚合操作(若无需聚合,仅返回每组第一条数据)。其去重能力是分组后的附加效果,具体机制如下:
- 分组规则:根据
GROUP BY 后指定的字段,将相同值的记录划分为一组,分组依据支持单列或多列。
- 聚合与去重:若 SQL 中包含 SUM、COUNT 等聚合函数,对每组计算结果;若无聚合函数,MySQL 会返回每组的“任意一条记录”(不同版本默认返回行可能不同,不建议依赖此特性)。
- 排序特性:这是影响其效率的关键——MySQL 8.0 之前会对分组字段进行“隐式排序”,8.0 及之后已移除该特性。
实操示例:
-- 单列分组去重(无聚合,仅返回每组第一条)
SELECT name FROM user GROUP BY name;
-- 分组+聚合,统计每组人数
SELECT name, COUNT(*) AS total FROM user GROUP BY name;
GROUP BY 的“隐式排序”
MySQL 8.0 之前,GROUP BY 的效率问题核心源于“隐式排序”,这一特性不仅会增加额外开销,还可能触发低效的磁盘操作。在 MySQL 5.7 及更早版本,GROUP BY 会默认对分组字段进行升序排序。此时存在两种情况:
- 有索引场景:若分组字段存在有序索引(如 B+ 树索引),
GROUP BY 可直接利用索引的有序性,无需额外排序,效率与 DISTINCT 一致。
- 无索引场景:MySQL 需先扫描全表数据,生成临时表存储分组结果,再对临时表进行排序(触发 filesort)。若结果集过大,临时表会超出内存限制,被写入磁盘(磁盘 IO 开销远高于内存操作),导致执行效率急剧下降。
针对 MySQL 5.7 及之前版本,可通过添加 ORDER BY NULL 强制禁止隐式排序,优化执行效率:
-- 禁止隐式排序,提升无索引场景下的效率
SELECT name FROM user GROUP BY name ORDER BY NULL;
此外,为解决 GROUP BY 隐式排序带来的性能问题,MySQL 8.0 对其进行了核心优化,主要变化如下:
- 移除隐式排序:
GROUP BY 不再默认对分组字段排序,查询结果的顺序不再固定(若需指定顺序,需手动添加 ORDER BY 子句)。
- 效率提升:无索引场景下,
GROUP BY 无需额外排序操作,执行效率与 DISTINCT 基本持平,无需再依赖 ORDER BY NULL 优化。
- 兼容性提醒:升级到 MySQL 8.0 后,依赖
GROUP BY 默认排序的 SQL 可能出现结果顺序变化,需手动补充 ORDER BY 确保一致性。
执行计划性能分析
执行计划是判断 SQL 效率的核心工具,通过 EXPLAIN 语句可直观看到 DISTINCT 与 GROUP BY 的执行逻辑差异。以下基于 「user表(无主键/索引,含name、age字段,10万条测试数据)」 ,分场景对比执行计划。
4.1 场景1:MySQL 5.7 + 无索引
核心验证:GROUP BY 是否触发隐式排序(filesort),DISTINCT 是否无额外排序开销。
EXPLAIN SELECT DISTINCT name FROM user;
执行计划关键字段:
| type |
Extra |
| ALL |
Using temporary |
解读:type 为 ALL(全表扫描),仅使用临时表存储去重结果,无 Using filesort(无额外排序),开销集中在全表扫描。
- GROUP BY执行计划(无ORDER BY NULL)
EXPLAIN SELECT name FROM user GROUP BY name;
执行计划关键字段:
| type |
Extra |
| ALL |
Using temporary; Using filesort |
解读:除全表扫描、临时表外,额外触发 Using filesort(隐式排序),双重开销导致效率低于 DISTINCT。
- GROUP BY执行计划(加ORDER BY NULL)
EXPLAIN SELECT name FROM user GROUP BY name ORDER BY NULL;
执行计划关键字段:
| type |
Extra |
| ALL |
Using temporary |
解读:Using filesort 消失,执行计划与 DISTINCT 一致,效率显著提升。
4.2 场景2:MySQL 8.0 + 无索引
核心验证:GROUP BY 是否已移除隐式排序,无需 ORDER BY NULL 优化。
EXPLAIN SELECT name FROM user GROUP BY name;
执行计划关键字段:
| type |
Extra |
| ALL |
Using temporary |
解读:无 Using filesort,执行计划与 DISTINCT 完全一致,两者效率持平。想了解更多关于数据库优化技术的探讨,可以访问云栈社区进行交流。
4.3 场景3:任意版本 + 有索引(name字段建普通索引)
核心验证:两者是否均能利用索引,消除全表扫描与排序开销。
-- 建索引
CREATE INDEX idx_user_name ON user(name);
-- DISTINCT执行计划
EXPLAIN SELECT DISTINCT name FROM user;
-- GROUP BY执行计划
EXPLAIN SELECT name FROM user GROUP BY name;
两者执行计划一致,关键字段:
| type |
key |
Extra |
| range |
idx_user_name |
Using index |
解读:type 为 range(索引范围扫描),利用 idx_user_name 索引完成去重/分组,Using index 表示直接通过索引获取结果,无全表扫描、临时表及排序开销,效率最优。这正是 数据库优化 中索引设计的核心价值体现。
4.4 执行计划核心结论
- 无索引时,MySQL 5.7 的
GROUP BY 因隐式排序多一层开销,8.0 版本已修复;
- 有索引时,两者均能高效利用索引,执行逻辑完全一致;
Using filesort 和 Using temporary 是性能瓶颈标识,需通过索引或版本优化规避。
如何选择合适的去重方式
结合底层机制、版本特性及执行计划分析,给出以下实操建议,平衡效率与代码可读性:
- 单纯去重场景:优先使用
DISTINCT,语法简洁且各版本效率稳定,无隐式排序风险,降低维护成本。
- 分组聚合场景:仅能使用
GROUP BY,搭配 SUM、COUNT 等聚合函数实现业务需求,这是其核心适用场景。
- 索引优化优先:若需频繁对某字段去重/分组,为该字段建立普通索引(如 B+ 树索引),可直接利用索引有序性,消除全表扫描和排序开销,是效率优化的核心手段。
- 版本适配技巧:MySQL 5.7 及之前,使用
GROUP BY 必须添加 ORDER BY NULL,规避隐式排序触发 filesort;MySQL 8.0 及之后:无需额外优化 GROUP BY 排序,仅按需添加 ORDER BY 指定结果顺序即可。对于高并发系统,深入理解这类查询优化是后端架构设计的重要一环。
总结
DISTINCT 与 GROUP BY 并非对立关系,核心差异在于设计初衷:DISTINCT 专注于“去重”,逻辑简单高效;GROUP BY 专注于“分组聚合”,去重仅为附加能力。两者的效率差异本质是 GROUP BY 的隐式排序导致,而 MySQL 8.0 的优化已大幅缩小这一差距。
实际开发中,需结合业务场景(是否需要聚合)、MySQL 版本及索引情况选择合适方式,既能保证执行效率,又能提升代码可读性。