被问懵了?MySQL 面试:DISTINCT 和 GROUP BY 效率到底谁更高?

76 阅读5分钟

在 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 能够更加简洁明了。