在 MySQL 中,DISTINCT 和 GROUP BY 都可以用来去重,但它们在性能上可能存在差异。
很多人会在写 SQL 时纠结到底该用 DISTINCT 还是 GROUP BY,并且特别关心在大数据量的情况下到底哪种方式执行更快呢?
先说大致结论
“
有索引的情况下,DISTINCT 和 GROUP BY 的效率几乎相同。
无索引的情况下,DISTINCT 通常比 GROUP BY 效率更高
尤其是在 MySQL 8.0 之前版本,GROUP BY 会触发隐式排序,可能导致额外的文件排序(filesort),进而降低效率。
MySQL 8.0 及之后的版本,MySQL 优化器做了改进,GROUP BY 默认不再进行隐式排序,二者在无索引情况下的效率更加接近。
DISTINCT 和 GROUP BY 的基础用法
DISTINCT 的使用
DISTINCT 是用来去重的一个 SQL 关键字,能够让查询结果返回唯一的记录。
它的基本语法如下:
SELECT DISTINCT column1, column2 FROM table_name WHERE conditions;
单列去重
SELECT DISTINCT age FROM student;
执行后,age 列只会返回唯一值,即使该列中有重复的数据,MySQL 也会将其去重。
结果可能如下所示:
+------+
| age |
+------+
| 10 |
| 12 |
| 11 |
| NULL |
+------+
“
值得注意的是,DISTINCT 会将 NULL 视为一个独立的值,因此即使有多个 NULL,它也只会保留一个。
多列去重
如果你需要基于多列进行去重,DISTINCT 也可以支持,只有当多列都相同时才会被视为重复。
例如:
SELECT DISTINCT sex, age FROM student;
查询结果可能如下:
+--------+------+
| sex | age |
+--------+------+
| male | 10 |
| female | 12 |
| male | 11 |
| male | NULL |
| female | 11 |
+--------+------+
在这里,只有 sex 和 age 都相同的行才会被视为重复记录,MySQL 才会去除这些行。
GROUP BY 的使用
GROUP BY 主要用于对查询结果进行分组。
语法如下:
SELECT column1, column2 FROM table_name WHERE conditions GROUP BY column1, column2;
单列去重
GROUP BY 也可以实现类似 DISTINCT 的效果,
例如:
SELECT age FROM student GROUP BY age;
输出结果如下:
+------+
| age |
+------+
| 10 |
| 12 |
| 11 |
| NULL |
+------+
多列去重
当我们使用多列进行 GROUP BY,效果与 DISTINCT 类似。
例如:
SELECT sex, age FROM student GROUP BY sex, age;
结果如下:
+--------+------+
| sex | age |
+--------+------+
| male | 10 |
| female | 12 |
| male | 11 |
| male | NULL |
| female | 11 |
+--------+------+
可以看出,GROUP BY 和 DISTINCT 在实现上有很多相似之处,但二者的差异主要体现在如何处理分组和排序。
DISTINCT 与 GROUP BY 的原理与性能
DISTINCT 和 GROUP BY 的底层实现
在 MySQL 中,DISTINCT 和 GROUP BY 都依赖于分组操作。在某些情况下,它们甚至可以看作是等效的。
例如,下面的两条 SQL 查询:
SELECT DISTINCT int1_index FROM test_table;
SELECT int1_index FROM test_table GROUP BY int1_index;
对于这两条查询,MySQL 优化器可能会使用相同的执行计划,尤其是在 int1_index 上有索引时。都可以利用索引进行优化,因此查询性能非常接近。
通过 EXPLAIN 我们可以看出,在一些情况下,二者都可以通过索引扫描来优化:
mysql> explain select distinct int1_index from test_distinct_groupby;
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | test_distinct_groupby | NULL | range | index_1 | index_1 | 5 | NULL | 955 | 100.00 | Using index for group-by |
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
1 row in set (0.05 sec)
结果显示 Using index,说明查询使用了索引扫描。
同样,下面的 GROUP BY 查询也使用了索引扫描:
mysql> explain select int1_index from test_distinct_groupby group by int1_index;
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | test_distinct_groupby | NULL | range | index_1 | index_1 | 5 | NULL | 955 | 100.00 | Using index for group-by |
+----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
1 row in set (0.05 sec)
因此,有索引的情况下,二者的执行效率基本一致。
MySQL 8.0 之前的 GROUP BY 隐式排序
在 MySQL 8.0 之前,GROUP BY 会默认对结果集进行隐式排序。这意味着即使你不显式指定 ORDER BY,GROUP BY 也会进行排序操作,导致可能触发 filesort(文件排序)。
例如:
SELECT int6_bigger_random FROM test_table GROUP BY int6_bigger_random;
通过 EXPLAIN,你可以看到它触发了 Using filesort:
+----+-------------+-------+------+----------+-------------+
| id | select_type | table | rows | Extra |
+----+-------------+-------+------+----------+-------------+
| 1 | SIMPLE | test | 97402| Using filesort |
+----+-------------+-------+------+----------+-------------+
在这种情况下,GROUP BY 会因为隐式排序,导致效率低于 DISTINCT,尤其是在大数据量的情况下。
MySQL 8.0 及之后版本的优化
从 MySQL 8.0 开始,GROUP BY 不再默认进行隐式排序。这一改进极大提升了 GROUP BY 的性能。在新版本中,如果你不需要排序,MySQL 8.0 不会强制进行排序操作,避免了不必要的 filesort。
因此,在 MySQL 8.0 之后,GROUP BY 和 DISTINCT 的性能差异缩小,二者效率趋于一致。
总结
有索引的情况下
DISTINCT 和 GROUP BY 的效率相同。二者都可以使用索引进行优化,MySQL 优化器能够高效地执行查询,性能差异微乎其微。
无索引的情况下
在 MySQL 8.0 之前,DISTINCT 效率高于 GROUP BY。这是因为 GROUP BY 默认进行隐式排序,可能导致额外的排序操作,增加了执行时间。
在 MySQL 8.0 之后,DISTINCT 和 GROUP BY 效率接近。MySQL 8.0 取消了隐式排序,这使得 GROUP BY 在无索引情况下的效率与 DISTINCT 相差无几。
实际应用中的建议
虽然 DISTINCT 和 GROUP BY 在某些场景下可以互换,但从语义清晰度和灵活性上看,GROUP BY 更适合处理复杂的数据分组和聚合操作。你可以使用 GROUP BY 搭配 HAVING 以及聚合函数,进行更为复杂的业务处理。
推荐使用 GROUP BY:
当需要对数据进行复杂处理时,GROUP BY 提供了更灵活的操作能力。 使用 DISTINCT:在只需要简单去重的场景下,DISTINCT 能够更加简洁明了。