满足GROUP BY子句的最一般的方法是扫描整个表并创建一个新的临时表,表中每个组的所有行应为连续的,然后使用该临时表来找到组并应用累积函数(如果有)。在某些情况中,MySQL能够做得更好,即通过索引访问而不用创建临时表。

为GROUP BY使用索引的最重要的前提条件是所有GROUP BY列引用同一索引的属性,并且索引按顺序保存其关键字。是否用索引访问来代替临时表的使用还取决于在查询中使用了哪部分索引、为该部分指定的条件,以及选择的累积函数。

由于GROUP BY 实际上也同样会进行排序操作,而且与ORDER BY 相比,GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。在MySQL 中,GROUP BY 的实现同样有多种(三种)方式,其中有两种方式会利用现有的索引信息来完成 GROUP BY,另外一种为完全无法使用索引的场景下使用。下面我们分别针对这三种实现方式做一个分析。

1、使用松散索引扫描(Loose index scan)实现 GROUP BY

对“松散索引扫描”的定义,本人看了很多网上的介绍,都不甚明白。在此逻列如下:

定义1:松散索引扫描,实际上就是当 MySQL 完全利用索引扫描来实现 GROUP BY 的时候,并不需要扫描所有满足条件的索引键即可完成操作得出结果。

定义2:优化Group By最有效的办法是当可以直接使用索引来完全获取需要group的字段。使用这个访问方法时,MySQL使用对关键字排序的索引的类型(比如BTREE索引)。这使得索引中用于group的字段不必完全涵盖WHERE条件中索引对应的key。由于只包含索引中关键字的一部分,因此称为松散的索引扫描。
意思是索引中用于group的字段,没必要包含多列索引的全部字段。例如:有一个索引idx(c1,c2,c3),那么group by c1、group by c1,c2这样c1或c1、c2都只是索引idx的一部分。要注意的是,索引中用于group的字段必须符合索引的“最左前缀”原则。group by c1,c3是不会使用松散的索引扫描的

例如:

explain
SELECT group_id,gmt_create
FROM group_message
WHERE user_id>1
GROUP BY group_id,gmt_create;

本人理解“定义2”的例子说明

有一个索引idx(c1,c2,c3)

SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

索引中用于group的字段为c1,c2
不必完全涵盖WHERE条件中索引对应的key(where条件中索引,即为c1;c1对应的key,即为idx)
索引中用于group的字段(c1,c2)只包含索引中关键字(c1,c2,c3)的一部分,因此称为松散的索引扫描。

要利用到松散索引扫描实现GROUP BY,需要至少满足以下几个条件:

◆ 查询针对一个单表

◆ GROUP BY 条件字段必须在同一个索引中最前面的连续位置;
GROUP BY包括索引的第1个连续部分(如果对于GROUP BY,查询有一个DISTINCT子句,则所有DISTINCT的属性指向索引开头)。

◆ 在使用GROUP BY 的同时,如果有聚合函数,只能使用 MAX 和 MIN 这两个聚合函数,并且它们均指向相同的列。

◆ 如果引用(where条件中)到了该索引中GROUP BY 条件之外的字段条件的时候,必须以常量形式存在,但MIN()或MAX() 函数的参数例外;
   或者说:索引的任何其它部分(除了那些来自查询中引用的GROUP BY)必须为常数(也就是说,必须按常量数量来引用它们),但MIN()或MAX() 函数的参数例外。

补充:如果sql中有where语句,且select中引用了该索引中GROUP BY 条件之外的字段条件的时候,where中这些字段要以常量形式存在。

◆ 如果查询中有where条件,则条件必须为索引,不能包含非索引的字段

松散索引扫描
explain
SELECT group_id,user_id
FROM group_message
WHERE group_id between 1 and 4
GROUP BY group_id,user_id;

松散索引扫描
explain
SELECT group_id,user_id
FROM group_message
WHERE user_id>1 and group_id=1
GROUP BY group_id,user_id;

非松散索引扫描
explain
SELECT group_id,user_id
FROM group_message
WHERE abc=1
GROUP BY group_id,user_id;

非松散索引扫描
explain
SELECT group_id,user_id
FROM group_message
WHERE user_id>1 and abc=1
GROUP BY group_id,user_id;

松散索引扫描,此类查询的EXPLAIN输出显示Extra列的Using index for group-by

下面的查询提供该类的几个例子,假定表t1(c1,c2,c3,c4)有一个索引idx(c1,c2,c3):

SELECT c1, c2 FROM t1 GROUP BY c1, c2;

SELECT DISTINCT c1, c2 FROM t1;

SELECT c1, MIN(c2) FROM t1 GROUP BY c1;

SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;

SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;

由于上述原因,不能用该快速选择方法执行下面的查询:

1、除了MIN()或MAX(),还有其它累积函数,例如:

SELECT c1, SUM(c2) FROM t1 GROUP BY c1;

2、GROUP BY子句中的域不引用索引开头,如下所示:

SELECT c1,c2 FROM t1 GROUP BY c2, c3;

3、查询引用了GROUP BY部分后面的关键字的一部分,并且没有等于常量的等式,例如:

SELECT c1,c3 FROM t1 GROUP BY c1, c2;

这个例子中,引用到了c3(c3必须为组合索引中的一个),因为group by 中没有c3。并且没有等于常量的等式。所以不能使用松散索引扫描

可以这样改一下:SELECT c1,c3 FROM t1 where c3='a' GROUP BY c1, c2

下面这个例子不能使用松散索引扫描

SELECT c1,c3 FROM t1 where c3='a' GROUP BY c1, c2

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

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

2、使用紧凑索引扫描(Tight index scan)实现 GROUP BY

紧凑索引扫描实现 GROUP BY 和松散索引扫描的区别主要在于:

紧凑索引扫描需要在扫描索引的时候,读取所有满足条件的索引键,然后再根据读取出的数据来完成 GROUP BY 操作得到相应结果。

这时候的执行计划的 Extra 信息中已经没有“Using index for group-by”了,但并不是说 MySQL 的 GROUP BY 操作并不是通过索引完成的,只不过是需要访问 WHERE 条件所限定的所有索引键信息之后才能得出结果。这就是通过紧凑索引扫描来实现 GROUP BY 的执行计划输出信息。

在 MySQL 中,MySQL Query Optimizer 首先会选择尝试通过松散索引扫描来实现 GROUP BY 操作,当发现某些情况无法满足松散索引扫描实现 GROUP BY 的要求之后,才会尝试通过紧凑索引扫描来实现。

当 GROUP BY 条件字段并不连续或者不是索引前缀部分的时候,MySQL Query Optimizer 无法使用松散索引扫描。

这时检查where 中的条件字段是否有索引的前缀部分,如果有此前缀部分,且该部分是一个常量,且与group by 后的字段组合起来成为一个连续的索引。这时按紧凑索引扫描。

SELECT max(gmt_create)
FROM group_message
WHERE group_id = 2
GROUP BY user_id

需读取group_id=2的所有数据,然后在读取的数据中完成group by操作得到结果。(这里group by 字段并不是一个连续索引,正好where 中group_id正好弥补缺失的索引键,又恰好是一个常量,因此使用紧凑索引扫描)

group_id user_id 这个顺序是可以使用该索引。如果连接的顺序不符合索引的“最左前缀”原则,则不使用紧凑索引扫描。

以下例子使用紧凑索引扫描

GROUP BY中有一个差距,但已经由条件user_id = 1覆盖。

explain
SELECT group_id,gmt_create
FROM group_message
WHERE user_id = 1 GROUP BY group_id,gmt_create

GROUP BY不以关键字的第1个元素开始,但是有一个条件提供该元素的常量
explain
SELECT group_id,gmt_create
FROM group_message
WHERE group_id = 1 GROUP BY user_id,gmt_create

下面的例子都不使用紧凑索引扫描

user_id,gmt_create 连接起来并不符合索引“最左前缀”原则
explain
SELECT group_id,gmt_create
FROM group_message
WHERE user_id = 1 GROUP BY gmt_create

group_id,gmt_create 连接起来并不符合索引“最左前缀”原则

explain
SELECT gmt_create
FROM group_message
WHERE group_id=1 GROUP BY gmt_create;

3、使用临时表实现 GROUP BY

MySQL Query Optimizer 发现仅仅通过索引扫描并不能直接得到 GROUP BY 的结果之后,他就不得不选择通过使用临时表然后再排序的方式来实现 GROUP BY了。在这样示例中即是这样的情况。 group_id 并不是一个常量条件,而是一个范围,而且 GROUP BY 字段为 user_id。所以 MySQL 无法根据索引的顺序来帮助 GROUP BY 的实现,只能先通过索引范围扫描得到需要的数据,然后将数据存入临时表,然后再进行排序和分组操作来完成 GROUP BY。

explain
SELECT group_id
FROM group_message
WHERE group_id between 1 and 4
GROUP BY user_id;

示例数据库文件

    1. -- --------------------------------------------------------
    2. -- Host:                         127.0.0.1
    3. -- Server version:               5.1.57-community - MySQL Community Server (GPL)
    4. -- Server OS:                    Win32
    5. -- HeidiSQL version:             7.0.0.4156
    6. -- Date/time:                    2012-08-20 16:52:10
    7. -- --------------------------------------------------------
    8. /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
    9. /*!40101 SET NAMES utf8 */;
    10. /*!40014 SET FOREIGN_KEY_CHECKS=0 */;
    11. -- Dumping structure for table test.group_message
    12. DROP TABLE IF EXISTS `group_message`;
    13. CREATE TABLE IF NOT EXISTS `group_message` (
    14. `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    15. `group_id` int(10) unsigned DEFAULT NULL,
    16. `user_id` int(10) unsigned DEFAULT NULL,
    17. `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    18. `abc` int(11) NOT NULL DEFAULT '0',
    19. PRIMARY KEY (`id`),
    20. KEY `group_id_user_id_gmt_create` (`group_id`,`user_id`,`gmt_create`)
    21. ) ENGINE=MyISAM AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
    22. -- Dumping data for table test.group_message: 0 rows
    23. DELETE FROM `group_message`;
    24. /*!40000 ALTER TABLE `group_message` DISABLE KEYS */;
    25. INSERT INTO `group_message` (`id`, `group_id`, `user_id`, `gmt_create`, `abc`) VALUES
    26. (1, 1, 1, '2012-08-20 09:25:35', 1),
    27. (2, 2, 1, '2012-08-20 09:25:39', 1),
    28. (3, 2, 2, '2012-08-20 09:25:47', 1),
    29. (4, 3, 1, '2012-08-20 09:25:50', 2),
    30. (5, 3, 2, '2012-08-20 09:25:52', 2),
    31. (6, 3, 3, '2012-08-20 09:25:54', 0),
    32. (7, 4, 1, '2012-08-20 09:25:57', 0),
    33. (8, 4, 2, '2012-08-20 09:26:00', 0),
    34. (9, 4, 3, '2012-08-20 09:26:02', 0),
    35. (10, 4, 4, '2012-08-20 09:26:06', 0),
    36. (11, 5, 1, '2012-08-20 09:26:09', 0),
    37. (12, 5, 2, '2012-08-20 09:26:12', 0),
    38. (13, 5, 3, '2012-08-20 09:26:13', 0),
    39. (14, 5, 4, '2012-08-20 09:26:15', 0),
    40. (15, 5, 5, '2012-08-20 09:26:17', 0),
    41. (16, 6, 1, '2012-08-20 09:26:20', 0),
    42. (17, 7, 1, '2012-08-20 09:26:23', 0),
    43. (18, 7, 2, '2012-08-20 09:26:28', 0),
    44. (19, 8, 1, '2012-08-20 09:26:32', 0),
    45. (20, 8, 2, '2012-08-20 09:26:35', 0),
    46. (21, 9, 1, '2012-08-20 09:26:37', 0),
    47. (22, 9, 2, '2012-08-20 09:26:40', 0),
    48. (23, 10, 1, '2012-08-20 09:26:42', 0),
    49. (24, 10, 2, '2012-08-20 09:26:44', 0),
    50. (25, 10, 3, '2012-08-20 09:26:51', 0),
    51. (26, 11, 1, '2012-08-20 09:26:54', 0);
    52. /*!40000 ALTER TABLE `group_message` ENABLE KEYS */;
    53. /*!40014 SET FOREIGN_KEY_CHECKS=1 */;
    54. /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_C

MySQL优化GROUP BY-松散索引扫描与紧凑索引扫描的更多相关文章

  1. MySQL松散索引扫描与紧凑索引扫描

    什么是松散索引? 答:实际上就是当MySQL 完全利用索引扫描来实现GROUP BY 的时候,并不需要扫描所有满足条件的索引键即可完成操作得出结果. 要利用到松散索引扫描实现GROUP BY,需要至少 ...

  2. mysql 优化之 is null ,is not null 索引使用测试

    关于mysql优化部分,有很多网友说尽量避免使用is null, is not null,select * 等,会导致索引失效,性能降低?那是否一定收到影响呢?真的就不会使用索引了吗? 本文的测试数据 ...

  3. MySQL如何优化GROUP BY :松散索引扫描 VS 紧凑索引扫描

    执行GROUP BY子句的最一般的方法:先扫描整个表,然后创建一个新的临时表,表中每个组的所有行应为连续的,最后使用该临时表来找到组 并应用聚集函数.在某些情况中,MySQL通过访问索引就可以得到结果 ...

  4. 第九课——MySQL优化之索引和执行计划

    一.创建索引需要关注什么? 1.关注基数列唯一键的数量: 比如性别,该列只有男女之分,所以性别列基数是2: 2.关注选择性列唯一键与行数的比值,这个比值范围在0~1之前,值越小越好: 其实,选择性列唯 ...

  5. mysql优化工具(索引优化)

    mysql优化工具 1.pt-duplicate-key-checker(检查数据库的重复索引),这款工具可以帮助我们找到重复的索引并且还会给你删除重复索引的建议语句,非常好用. 2.

  6. mysql 优化2

    6. 合理使用EXISTS,NOT EXISTS子句.如下所示: 1.SELECT SUM(T1.C1) FROM T1 WHERE (SELECT COUNT(*)FROM T2 WHERE T2. ...

  7. mysql数据库优化课程---15、mysql优化步骤

    mysql数据库优化课程---15.mysql优化步骤 一.总结 一句话总结:索引优化最立竿见影 1.mysql中最常用最立竿见影的优化是什么? 索引优化 索引优化,不然有多少行要扫描多少次,1亿行大 ...

  8. mysql 松散索引与紧凑索引扫描(引入数据结构)

    这一篇文章本来应该是放在 mysql 高性能日记中的,并且其优化程度并不高,但考虑到其特殊性和原理(索引结构也在这里稍微讲一下) 一,mysql 索引结构 (B.B+树) 要问到 mysql 的索引用 ...

  9. mysql 通过使用联全索引优化Group by查询

    /*SELECT count(*) FROM (*/ EXPLAIN SELECT st.id,st.Stu_name,tmpgt.time,tmpgt.goutong FROM jingjie_st ...

随机推荐

  1. 微软源代码管理工具TFS2013安装与使用图文教程

    微软源代码管理工具TFS2013安装与使用图文教程 这篇文章主要介绍了微软源代码管理工具TFS2013安装与使用图文教程,本文详细的给出了TFS2013的安装配置过程.使用教程,需要的朋友可以参考下 ...

  2. Excel在任务栏中只显示一个窗口的解决办法

     Excel在任务栏中只显示一个窗口的解决办法  以前朋友遇到过这个问题,这次自己又遇到了,习惯了以前的那种在任务栏中显示全部窗口,方便用Alt+Tab键进行切换. 如果同时打开许多Excel工作簿, ...

  3. EasyUI两种动态添加tab Iframe页面的方法

    /** 动态添加tab-----方式一 **/ function addIframeTab(titleTxt,href,icon) { $('#mytabs').tabs('addIframeTab' ...

  4. 简单Demo的用户登录JSP界面IE、Firefox(chrome) Enter 键提交表单

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  5. 链表k个节点反向

    问题: 以k个元素为一组,反转单向链表.比如: 输入: 1->2->3->4->5->6->7->8->null and k = 3 输出:3-> ...

  6. linux grep练习

    1.显示/proc/meminfo文件中以不区分大小的s开头的行: 2.显示/etc/passwd中以nologin结尾的行; 3.显示/etc/inittab中以#开头,且后面跟一个或多个空白字符, ...

  7. KMP快速模式匹配的java实现

    假期实在无聊赖啊.把这个算法实现了一下即算是打发时间也算练练手了. KMP算法的关键是用归纳法计算失败函数.网上很详细了.下面直接给出代码. /** * * @author Vincent * */ ...

  8. stage.width/height和stage.stageWidth/stageHeight的区别

    stage.stageWidth和stage.stageHeight就是舞台的宽带和高度 一般默认打开宽带是550,高度是400 那么stage.stageWidth=550,stage.stageH ...

  9. c++单元测试框架googletest

    一.概述 Googletest是一个用来写C++单元测试的框架,它是跨平台的,可应用在windows.linux.Mac等OS平台上: 代码框架: [root@docker googletest-re ...

  10. Linux下命令行显示当前全路径方法

    /etc/profile 和 ~/.bashrc 或者直接在用户的.bash_profile中添加 export PS1='[\u@\h \W]\$' 然后执行source命令