京东面试原题:深入分析MySQL中的DISTINCT和GROUP BY的性能差异与使用场景

初步结论(详尽分析见文末):

  • 在相同语义且有索引的情况下,GROUP BYDISTINCT的性能相当,因为它们都能利用索引。
  • 在相同语义且无索引的情况下,DISTINCT的效率通常高于GROUP BY。这是因为尽管两者都进行分组操作,GROUP BY可能会触发排序过程,导致额外的执行开销。

基于上述结论,可能会引发以下疑问:

  • 为什么在相同语义且有索引的情况下,GROUP BYDISTINCT的性能会相同?
  • 在哪些情况下GROUP BY会进行排序操作?

接下来,我们将探讨DISTINCTGROUP 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 BYDISTINCT的用法类似。

单列去重

语法:

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。它们的实现依赖于分组操作,且都能通过松散索引扫描或紧凑索引扫描来实现。

DISTINCTGROUP 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  

这说明在一般情况下,对于相同语义的DISTINCTGROUP 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 BYDISTINCT的效率一致。由于DISTINCT几乎可以被视作特殊的GROUP BY
  • 在相同语义且无索引的情况下, DISTINCT效率高于GROUP BY。这是因为在MySQL 8.0之前,GROUP BY会进行隐式排序,从而导致性能下降。而在8.0版本之后,GROUP BY不再执行隐式排序,二者的执行效率几乎相同。

相较于DISTINCTGROUP BY的语义更为明确。由于DISTINCT关键字会对所有列生效,因此在进行复合业务处理时,GROUP BY提供了更高的灵活性。它能够基于分组情况对数据进行更复杂的处理,例如使用HAVING对子集数据进行过滤,或利用聚合函数进行计算。