京东面试原题:深入分析MySQL中的DISTINCT和GROUP BY的性能差异与使用场景
初步结论(详尽分析见文末):
- 在相同语义且有索引的情况下,
GROUP BY
和DISTINCT
的性能相当,因为它们都能利用索引。 - 在相同语义且无索引的情况下,
DISTINCT
的效率通常高于GROUP BY
。这是因为尽管两者都进行分组操作,GROUP BY
可能会触发排序过程,导致额外的执行开销。
基于上述结论,可能会引发以下疑问:
- 为什么在相同语义且有索引的情况下,
GROUP BY
和DISTINCT
的性能会相同? - 在哪些情况下
GROUP BY
会进行排序操作?
接下来,我们将探讨DISTINCT
和GROUP BY
的基本用法。
DISTINCT的用法
DISTINCT基本用法
SELECT DISTINCT columns FROM table_name WHERE where_conditions;
例如:
mysql> SELECT DISTINCT age FROM student;
+------+
| age |
+------+
| 10 |
| 12 |
| 11 |
| NULL |
+------+
4 rows in set (0.01 sec)
DISTINCT
关键字用于返回唯一的不同值。它位于查询语句的第一列前,并对主句的所有列适用。
如果某列包含NULL值,并且该列使用了DISTINCT
,MySQL将保留一个NULL值并忽略其他NULL值,因为DISTINCT
会将所有NULL视为相同值。
多列去重
要对多列进行去重,可以根据指定的列信息进行,只有当所有指定列的值都相同,才会被视为重复。
SELECT DISTINCT column1, column2 FROM table_name WHERE where_conditions;
mysql> SELECT DISTINCT sex, age FROM student;
+--------+------+
| sex | age |
+--------+------+
| male | 10 |
| female | 12 |
| male | 11 |
| male | NULL |
| female | 11 |
+--------+------+
5 rows in set (0.02 sec)
GROUP BY的用法
在基础去重方面,GROUP BY
与DISTINCT
的用法类似。
单列去重
语法:
SELECT columns FROM table_name WHERE where_conditions GROUP BY columns;
执行:
mysql> SELECT age FROM student GROUP BY age;
+------+
| age |
+------+
| 10 |
| 12 |
| 11 |
| NULL |
+------+
4 rows in set (0.02 sec)
多列去重
语法:
SELECT columns FROM table_name WHERE where_conditions GROUP BY columns;
执行:
mysql> SELECT sex, age FROM student GROUP BY sex, age;
+--------+------+
| sex | age |
+--------+------+
| male | 10 |
| female | 12 |
| male | 11 |
| male | NULL |
| female | 11 |
+--------+------+
5 rows in set (0.03 sec)
DISTINT与GROUP BY的区别
两者在语法上的主要区别在于,GROUP BY
可以针对单列进行去重。GROUP BY
的执行原理是先对结果集进行分组,然后返回每组的第一条记录,依据的是GROUP BY
后接的字段。
例如:
mysql> SELECT sex, age FROM student GROUP BY sex;
+--------+-----+
| sex | age |
+--------+-----+
| male | 10 |
| female | 12 |
+--------+-----+
2 rows in set (0.03 sec)
DISTINCT与GROUP BY的原理
在绝大多数情况下,DISTINCT
可以视作特殊的GROUP BY
。它们的实现依赖于分组操作,且都能通过松散索引扫描或紧凑索引扫描来实现。
DISTINCT
和GROUP BY
都可以利用索引进行扫描。例如,以下两条SQL语句的执行计划显示它们的EXTRA
信息均表明使用了Using index for group-by
:
mysql> EXPLAIN SELECT int1_index FROM test_distinct_groupby GROUP BY int1_index;
...
Extra: Using index for group-by
mysql> EXPLAIN SELECT DISTINCT int1_index FROM test_distinct_groupby;
...
Extra: Using index for group-by
这说明在一般情况下,对于相同语义的DISTINCT
和GROUP BY
语句,我们可以采用相同的索引优化策略。
然而,在MySQL 8.0之前,GROUP BY
默认会按字段进行隐式排序。
例如:
mysql> EXPLAIN SELECT int6_bigger_random FROM test_distinct_groupby GROUP BY int6_bigger_random;
...
Extra: Using temporary; Using filesort
隐式排序
对于隐式排序,MySQL官方的解释如下:
GROUP BY默认隐式排序(即在GROUP BY列没有ASC或DESC标识符的情况下)。然而,依赖于隐式排序或显式排序的GROUP BY已被弃用。为了生成特定的排序顺序,请提供ORDER BY子句。
简单来说,这意味着在MySQL 8.0之前,GROUP BY
会根据字段对结果进行隐式排序。若能利用索引进行排序,则GROUP BY
不需额外排序;但若无法利用索引,则MySQL优化器会采取临时表和排序的方式来实现GROUP BY
。
当结果集超出设置的临时表大小时,MySQL会将临时表数据复制到磁盘上进行操作,导致性能显著下降。因此,MySQL在8.0版本中对此进行了优化。
最终结论
- 在相同语义且有索引的情况下,
GROUP BY
和DISTINCT
的效率一致。由于DISTINCT
几乎可以被视作特殊的GROUP BY
。 - 在相同语义且无索引的情况下,
DISTINCT
效率高于GROUP BY
。这是因为在MySQL 8.0之前,GROUP BY
会进行隐式排序,从而导致性能下降。而在8.0版本之后,GROUP BY
不再执行隐式排序,二者的执行效率几乎相同。
相较于DISTINCT
,GROUP BY
的语义更为明确。由于DISTINCT
关键字会对所有列生效,因此在进行复合业务处理时,GROUP BY
提供了更高的灵活性。它能够基于分组情况对数据进行更复杂的处理,例如使用HAVING
对子集数据进行过滤,或利用聚合函数进行计算。