MySQL中DISTINCT与GROUP BY去重差异及性能测试

Jasper
Jasper
发布时间:2026-10-15| 6人浏览过
原创

在使用MySQL进行数据去重时,许多开发者首选DISTINCT关键字,但在处理复杂查询(如需要返回非去重字段)时往往会遇到意外结果。本文将对比DISTINCT与GROUP BY的底层逻辑差异,并通过实际代码演示错误原因,最后提供基于真实数据的性能测试结论。

DISTINCT与GROUP BY的业务逻辑差异

在业务开发中,当我们需要对某几列数据去重时,首先想到的通常是DISTINCT。然而,它的使用有其严格的限制:

1. DISTINCT的特性

DISTINCT的作用是对SELECT列表中的所有字段组合进行去重。只有当列表中所有字段的值完全相同时,才会被判定为重复记录并合并为一行。

  • 单列去重:仅指定一列时,简单有效,相同值只保留一个。
  • 多列去重:若指定多列,必须这些列的组合完全一致才算重复。如果在SELECT中包含了主键(如id),由于主键唯一性,DISTINCT实际上将不起任何去重作用。

2. GROUP BY的优势

GROUP BY的核心功能是按照指定字段进行分组,并通常配合聚合函数使用。但在没有聚合函数的情况下,它也可以用于去重。

  • 可以根据部分字段(如project_id)进行分组去重。
  • 在分组后,MySQL通常会从每组中选取第一条记录的其余字段(如company_name、id等)返回,这使得它在需要“保留分组依据字段外的其他详细信息”的场景下比DISTINCT更灵活。

实战案例:为什么我的DISTINCT没去重?

在基于MyBatis-Plus的实际开发中,我们常遇到如下困境:

场景:查询项目公司的去重数据,但希望同时返回project_id、company_id等非主键信息。

错误尝试一:仅DISTINCT非主键字段

QueryWrapper<ProjectCompany> wrapper = new QueryWrapper<>();
// 注意:这里只选了去重相关的业务字段,未选主键id
wrapper.select("DISTINCT project_id,company_id,company_name,is_delete")
       .eq("project_id", projectId)
       .eq("is_delete", "0");

上述代码执行后,返回的结果集可能为空或id为null(取决于ORM框架处理)。这是因为业务上的“重复”是基于这几列定义的,但数据库表中每行有唯一的id。如果我们不在SELECT中包含id,返回的对象属性映射会出现混乱,导致后续业务逻辑无法获取正确的记录ID。

错误尝试二:DISTINCT包含主键id

若在select中加入id,即 "DISTINCT id,project_id,..." ,由于id唯一,查出的数据将是全部数据,DISTINCT失效。

正确方案:使用GROUP BY

当我们只需要根据project_id去重,且需要获取对应的公司信息时,使用GROUP BY是更稳妥的选择。

LambdaQueryWrapper<ProjectCompany> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ProjectCompany::getProjectId, projectId)
       .eq(ProjectCompany::getIsDelete, "0")
       .groupBy(ProjectCompany::getProjectId);

这种方式能确保以project_id为组,并自然带回该组中的其他字段(如id),既实现了去重目的,又保留了业务所需的完整对象信息。

DISTINCT与GROUP BY的性能对比实验

很多观点认为DISTINCT比GROUP BY快,或者反之。为了验证这一点,我们在MySQL 5.7.17环境下进行了基准测试。实验排除了缓存干扰,主要考察在不同索引情况和不同离散度下的执行时间。

1. 测试环境准备

  • 数据库版本:MySQL 5.7.17
  • 存储引擎:InnoDB
  • 测试表结构:每个表包含id(自增主键)和value(测试字段)。
  • 数据量:10万条记录(tab_1至tab_4)。

2. 实验变量设计

表名 记录数 查询字段有无索引 不同值个数(离散度)
tab_1 100,000 No 3 (低离散度)
tab_2 100,000 Yes (idx_value) 3 (低离散度)
tab_3 100,000 No 10,000 (高离散度)
tab_4 100,000 Yes (idx_value) 10,000 (高离散度)

3. 生成数据的SQL过程

使用存储过程批量插入随机整数,确保数据分布符合预期。

-- 创建带索引和无索引的两类基础表
DROP TABLE IF EXISTS `tab_1`;
CREATE TABLE `tab_1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `value` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `tab_2`;
CREATE TABLE `tab_2` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `value` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_value` (`value`) -- 建立索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 复制表结构生成tab_3和tab_4
DROP TABLE IF EXISTS `tab_3`;
CREATE TABLE `tab_3` LIKE `tab_1`;

DROP TABLE IF EXISTS `tab_4`;
CREATE TABLE `tab_4` LIKE `tab_2`;
-- 调用存储过程填充数据
-- tab_1/tab_3无索引,tab_2/tab_4有索引
call generateRandomData('tab_1','value',3,100000);   -- value取值只有1,2,3
INSERT INTO tab_2 SELECT * FROM tab_1;

call generateRandomData('tab_3','value',10000,100000); -- value取值1~10000
INSERT INTO tab_4 SELECT * FROM tab_3;

4. 测试结果数据

表名 索引情况 DISTINCT耗时 GROUP BY耗时
tab_1 无索引 0.058s 0.059s
tab_2 有索引 0.030s 0.027s
tab_3 无索引 0.072s 0.073s
tab_4 有索引 0.047s 0.049s

结果分析:

  1. 性能几乎一致:在所有测试场景下,DISTINCT和GROUP BY的执行时间差均在毫秒级别(甚至更小),在实际生产环境的感知中无明显区别。
  2. 索引的影响显著:启用索引后,两者的速度均有大幅提升(例如从0.072s降至0.047s左右)。
  3. 离散度的影响:不同值的数量增加会导致全表扫描时的去重成本略微上升,但对于百万级以下的数据量,这种差异可忽略不计。

总结与建议

如何选择?

  • 如果只需单纯过滤掉重复行,且返回所有列,两者皆可,优先使用DISTINCT语义更直观。
  • 如果需要按某些字段去重,同时保留其他字段的具体值(如最近的一条、金额最大的一条等),**强烈建议使用GROUP BY**。对于复杂的多字段关联去重,通常还需要结合ROW_NUMBER()窗口函数或子查询优化来实现,单纯的DISTINCT无法满足“取特定行”的需求。

性能建议:

无论使用哪种方式,确保去重涉及的字段上有合适的索引,是提升查询效率最关键的手段。在不涉及大量聚合运算的场景下,不必纠结于DISTINCT和GROUP BY的微秒级性能差异,应根据代码可读性和业务需求的灵活性进行选择。

以上就是MySQL中去重操作DISTINCT和GROUP BY的区别浅析及性能测试的详细内容,更多关于MySQL去重技巧与优化的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。