MySQL中的distinct和group by哪个效率更高?

前言

大家好,我是月夜枫~~

一、distinct和group by的区别

1.1.作用方式和应用场景

group by和‌distinct的主要区别在于它们的作用方式和应用场景。 group by用于对数据进行分组和聚合操作,通常与聚合函数(如COUNT、SUM、AVG等)一起使用,用于生成汇总信息。而distinct则用于从查询结果中选择唯一的值,主要用于数据清理或统计。‌12

group by的作用方式和应用场景:group by语句用于将数据按照一个或多个列进行分组,然后对每个组应用聚合函数(如COUNT、SUM、AVG等)以得到每个组的统计结果。例如,按照商品类别分组并统计每个类别的商品数量。group by通常用于在查询中生成汇总信息,并且可以处理多个字段的分组,这使得它在处理复杂查询时非常有用。‌12

distinct的作用方式和应用场景:distinct语句用于从查询结果中选择唯一的值。它可以用于从一个或多个列中选择唯一的值,也可以用于选择整行的唯一值。例如,按照商品类别查询所有不同的类别。distinct主要用于数据清理或统计,确保查询结果中的值是不重复的。

总结两者的优缺点:在使用时,应根据具体需求选择合适的去重方式。group by通常会产生比distinct更多的结果,因为它按照指定的列进行分组,而每个分组内可能包含多个不同的值。相比之下,distinct主要用于选择不重复的值,但在处理大量数据时,group by的性能通常优于distinct,因为它类似于先建立索引再查索引的过程。‌

distinct 关键字用于从 mysql 查询结果中去除重复行,仅保留唯一值。其用法包括:distinct column_name:从指定列中去除重复值。distinct(column_name1, column_name2, ...):从多个列的组合中去除重复值。

group by的意思为"分组查询",可以理解为"根据(by)一定的规则进行分组(group)";作用是通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小区域进行数据处理。在mysql中,group by是select语句的可选子句,用于根据一个或多个字段对查询结果进行分组,它减少了结果集中的行数。

  • 在语义相同,有索引的情况下:group by和distinct都能使用索引,效率相同。

  • 在语义相同,无索引的情况下:distinct效率高于group by。原因是distinct 和 group by都会进行分组操作,但group by可能会进行排序,触发filesort,导致sql执行效率低下。

带着这两个问题找答案。接下来,我们先来看一下distinct和group by的基础使用。

二、distinct的使用

2.1.distinct用法
sql 复制代码
SELECT DISTINCT columns FROM table_name WHERE where_conditions;

例如:

sql 复制代码
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值视为相同的值。

2.2.distinct多列去重

distinct多列的去重,则是根据指定的去重的列信息来进行,即只有所有指定的列信息都相同,才会被认为是重复的信息。

sql 复制代码
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类似:

3.1.单列去重

语法:

sql 复制代码
SELECT columns FROM table_name WHERE where_conditions GROUP BY columns;

执行:

sql 复制代码
mysql> select age from student group by age;
+------+
| age  |
+------+
|   10 |
|   12 |
|   11 |
| NULL |
+------+
4 rows in set (0.02 sec)
3.2.多列去重

语法:

sql 复制代码
SELECT columns FROM table_name WHERE where_conditions GROUP BY columns;

执行:

sql 复制代码
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)
3.3.区别示例

两者的语法区别在于,group by可以进行单列去重,group by的原理是先对结果进行分组排序,然后返回每组中的第一条数据。且是根据group by的后接字段进行去重的。

例如:

sql 复制代码
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的内容),我们对这两条sql进行分析,可以看到,在extra中,这两条sql都使用了紧凑索引扫描Using index for group-by

所以,在一般情况下,对于相同语义的DISTINCTGROUP BY语句,我们可以对其使用相同的索引优化手段来进行优化。

sql 复制代码
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> 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)

但对于GROUP BY来说,在MYSQL8.0之前,GROUP Y默认会依据字段进行隐式排序。

可以看到,下面这条sql语句在使用了临时表的同时,还进行了filesort。

mysql> explain select int6_bigger_random from test_distinct_groupby GROUP BY int6_bigger_random;
+----+-------------+-----------------------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
| id | select_type | table                 | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra                           |
+----+-------------+-----------------------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
|  1 | SIMPLE      | test_distinct_groupby | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 97402 |   100.00 | Using temporary; Using filesort |
+----+-------------+-----------------------+------------+------+---------------+------+---------+------+-------+----------+---------------------------------+
1 row in set (0.04 sec)
4.1.隐式排序

对于隐式排序,我们可以参考Mysql官方的解释:MySql官网

GROUP BY implicitly sorts by default (that is, in the absence of ASC or DESC designators for GROUP BY columns). However, relying on implicit GROUP BY sorting (that is, sorting in the absence of ASC or DESC designators) or explicit sorting for GROUP BY (that is, by using explicit ASC or DESC designators for GROUP BY columns) is deprecated. To produce a given sort order, provide an ORDER BY clause.

大致解释一下:

GROUP BY 默认隐式排序(指在 GROUP BY 列没有 ASC 或 DESC 指示符的情况下也会进行排序)。然而,GROUP BY进行显式或隐式排序已经过时(deprecated)了,要生成给定的排序顺序,请提供 ORDER BY 子句。

所以,在Mysql8.0之前,Group by会默认根据作用字段(Group by的后接字段)对结果进行排序。在能利用索引的情况下,Group by不需要额外进行排序操作;但当无法利用索引排序时,Mysql优化器就不得不选择通过使用临时表然后再排序的方式来实现GROUP BY了。

且当结果集的大小超出系统设置临时表大小时,Mysql会将临时表数据copy到磁盘上面再进行操作,语句的执行效率会变得极低。这也是Mysql选择将此操作(隐式排序)弃用的原因。

基于上述原因,Mysql在8.0时,对此进行了优化更新:Mysql8.0

Previously (MySQL 5.7 and lower), GROUP BY sorted implicitly under certain conditions. In MySQL 8.0, that no longer occurs, so specifying ORDER BY NULL at the end to suppress implicit sorting (as was done previously) is no longer necessary. However, query results may differ from previous MySQL versions. To produce a given sort order, provide an ORDER BY clause.

大致解释一下:

从前(Mysql5.7版本之前),Group by会根据确定的条件进行隐式排序。在mysql 8.0中,已经移除了这个功能,所以不再需要通过添加order by null 来禁止隐式排序了,但是,查询结果可能与以前的 MySQL 版本不同。要生成给定顺序的结果,请按通过ORDER BY指定需要进行排序的字段。

因此,我们的结论也出来了:

  • 在语义相同,有索引的情况下:

group by和distinct都能使用索引,效率相同。因为group by和distinct近乎等价,distinct可以被看做是特殊的group by

  • 在语义相同,无索引的情况下:

distinct效率高于group by。原因是distinct 和 group by都会进行分组操作,但group by在Mysql8.0之前会进行隐式排序,导致触发filesort,sql执行效率低下。

但从Mysql8.0开始,Mysql就删除了隐式排序,所以,此时在语义相同,无索引的情况下,group by和distinct的执行效率也是近乎等价的。

**五、**性能对比

在MySQL中,DISTINCT和GROUP BY的性能会受到多个因素的影响,包括表的大小、数据类型、查询条件等。因此,在具体应用中,需要根据实际情况进行选择。下面将对它们的性能进行比较。

5.1.数据类型

综上所述,对于小规模的数据集和简单的查询条件,使用DISTINCT和GROUP BY的性能差别不大,可以根据实际需要进行选择。但是,对于大规模的数据集和复杂的查询条件,建议使用DISTINCT。

在MySQL中,数据量的大小也会影响查询性能。如果数据量较小,那么DISTINCT和GROUP BY的性能差别不大。但是,如果数据量较大,那么GROUP BY的性能会明显受到影响,而DISTINCT的性能则相对较稳定。

这是因为GROUP BY需要对所有数据进行排序和聚合操作,而DISTINCT只需要对不重复的数据进行处理,因此在数据量较大的情况下,DISTINCT的性能相对更优。

5.2.索引

在MySQL中,索引可以大大提高查询性能。如果使用的是索引列,那么DISTINCT和GROUP BY的性能差别不大。但是,如果没有索引,那么GROUP BY的性能会受到较大影响,而DISTINCT的性能则相对较稳定。

5.3.查询条件

在MySQL中,对不同数据类型的处理方式是不同的。对于较小的数据类型(如整型和字符型),使用DISTINCT和GROUP BY的性能差别不大。但是,对于较大的数据类型(如文本类型和二进制类型),DISTINCT的性能要优于GROUP BY。

这是因为DISTINCT可以在内存中进行去重操作,而GROUP BY需要进行排序和聚合操作,因此需要更多的计算资源和时间。

5.4.数据量

在MySQL中,索引可以大大提高查询性能。如果使用的是索引列,那么DISTINCT和GROUP BY的性能差别不大。但是,如果没有索引,那么GROUP BY的性能会受到较大影响,而DISTINCT的性能则相对较稳定。

这是因为GROUP BY需要对所有数据进行排序和聚合操作,而DISTINCT只需要对不重复的数据进行处理,因此DISTINCT在没有索引的情况下相对更快。

5.5.结论

综上所述,对于小规模的数据集和简单的查询条件,使用DISTINCT和GROUP BY的性能差别不大,可以根据实际需要进行选择。但是,对于大规模的数据集和复杂的查询条件,建议使用DISTINCT。

如果查询条件需要使用HAVING子句进行过滤,或者需要返回每个组的统计信息,那么使用GROUP BY更为适合。但是,在使用GROUP BY时,需要注意对索引的优化,以避免影响查询性能。

六、优化建议

在实际应用中,为了提高查询性能,可以采取以下优化措施:

  • 使用合适的索引,以避免全表扫描和排序操作。
  • 尽量避免使用较大的数据类型,可以考虑使用VARCHAR替代TEXT等大型数据类型。
  • 尽量避免使用复杂的查询条件,可以将复杂的查询条件拆分为多个简单的条件。
  • 尽量减少不必要的字段查询,只查询必要的字段。
  • 尽量避免使用子查询和联合查询,可以考虑使用JOIN等更为高效的查询方式。

因此,根据具体情况选择合适的去重方式非常重要。如果数据较大,可以尝试使用DISTINCT来提高性能;如果查询条件较为复杂,可以使用DISTINCT来减少计算量。在实际应用中,可以根据表的大小、数据类型、索引情况和查询条件等因素进行选择。

6.1.为什么松散索引扫描的效率会很高?

因为在没有WHERE 子句,也就是必须经过全索引扫描的时候, 松散索引扫描需要读取的键值数量与分组的组数量一样多,也就是说比实际存在的键值数目要少很多;而在WHERE 子句包含范围判断式或者等值表达式的时候, 松散索引扫描查找满足范围条件的每个组的第1 个关键字,并且再次读取尽可能最少数量的关键字;

6.2.什么是紧凑索引?

紧凑索引扫描和松散索引扫描的区别主要在于,在扫描索引的时候,它需要读取所有满足条件的索引键,然后再根据读取的全部数据来完成GROUP BY 操作得到相应结果,没有跳过一些索引键;

例如,group by语句中的where条件中,对索引列等值查询和范围查询,执行计划是不同的,分别是走松散索引和紧凑索引;

如下:

sql 复制代码
# 表结构 联合索引(`c1`, `c2`, `c3`)
CREATE TABLE `t1` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `c1` VARCHAR(255) NULL DEFAULT '' COLLATE 'utf8_general_ci',
    `c2` VARCHAR(255) NULL DEFAULT '' COLLATE 'utf8_general_ci',
    `c3` VARCHAR(255) NULL DEFAULT '' COLLATE 'utf8_general_ci',
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `c` (`c1`, `c2`, `c3`) USING BTREE
)ENGINE=InnoDB;
 
# (1)SQL执行计划 "Using where; Using index" 使用紧凑索引扫描
explain SELECT DISTINCT `c1` FROM t1 WHERE `c2`>'B'; 
 
# (2)SQL执行计划 "Using where; Using index for group-by" 使用松散索引扫描
explain SELECT DISTINCT `c1` FROM t1 WHERE `c2`='B'; 

解释:因为对于组合索引,对组合键值有序;当做其中某个索引列的等值查询时,执行group by时还是能跳过一些不满足等值条件的行,这种情况走松散索引;但是做范围查询时,就需要找到每一行做范围匹配,因此走紧凑索引;

执行计划示例

下面也给出一些松散索引、紧凑索引和走临时表排序的示例;表结构同上表t1;

sql 复制代码
# (1)SQL执行计划 "Using where; Using index" 使用紧凑索引扫描,索引列范围查询
explain SELECT DISTINCT `c1` FROM t1 WHERE `c2`>'B'; 
 
# (2)SQL执行计划 "Using where; Using index for group-by" 使用松散索引扫描,索引列等值查询
explain SELECT DISTINCT `c1` FROM t1 WHERE `c2`='B'; 
 
# (3)SQL执行计划 "Using index for group-by" 使用松散索引扫描
explain SELECT MIN(c2) from t1 group by c1;
 
# (4)SQL执行计划 "Using index" 使用min/max之外的其它聚集函数,则不能使用松散索引扫描,使用紧凑索引扫描
explain SELECT SUM(c2) from t1 group by c1;
 
# (5)SQL执行计划 "Using index for group-by" 使用松散索引扫描,满足索引前缀
explain SELECT `c1`,`c2` FROM t1 GROUP BY `c1`,`c2`; 
 
# (6)SQL执行计划 "Using where; Using index for group-by" 使用松散索引扫描,满足索引前缀、索引列等值查询
explain SELECT `c1`,`c2`,`c3` FROM t1  WHERE c3='C' GROUP BY `c1`,`c2`; 
 
# (7)SQL执行计划 "Using where; Using index for group-by" 使用松散索引扫描,虽然不满足索引前缀,但前缀中的列为常量
explain SELECT `c1`,`c2`,`c3` FROM t1  WHERE c1='C' GROUP BY `c1`,`c2`,`c3`; 
 
# (8)SQL执行计划 "Using index; Using temporary; Using filesort",使用临时表,不满足前缀索引,分组无法走索引,需要临时表并对分组内元素排序
explain SELECT `c2`,`c3` FROM t1 GROUP BY `c2`,`c3`; 
 
# (9)SQL执行计划 "Using index; Using temporary; Using filesort",使用临时表,不满足前缀索引,分组无法走索引,需要临时表并对分组内元素排序
explain SELECT `c1`,`c3` FROM t1 GROUP BY `c1`,`c3`; 
 
# (10)SQL执行计划 "Using where; Using index",使用紧凑索引扫描,却别于(9)使用临时表,尽管不满足前缀索引,但前缀中的列为常量
explain SELECT `c1`,`c3` FROM t1 WHERE c2='B' GROUP BY `c1`,`c3`; 

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

我从清晨走过,也拥抱夜晚的星辰,人生没有捷径,你我皆平凡,你好,陌生人,一起共勉。

相关推荐
公贵买其鹿几秒前
List深拷贝后,数据还是被串改
java
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹4 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫5 小时前
泛型(2)
java
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石5 小时前
12/21java基础
java
李小白665 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp5 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea