SQLgroupby底层原理——本质是排序,可以利⽤索引事先排好
序
由于GROUP BY 实际上也同样会进⾏排序操作,⽽且与ORDER BY 相⽐,GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使⽤了其他的⼀些聚合函数,那么还需要⼀些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY⼀样也可以利⽤到索引。
在中,GROUP BY 的实现同样有多种(三种)⽅式,其中有两种⽅式会利⽤现有的索引信息来完成 GROUP BY,另外⼀种为完全⽆法使⽤索引的场景下使⽤。下⾯我们分别针对这三种实现⽅式做⼀个分析。
1、使⽤松散(Loose)索引扫描实现 GROUP BY
何谓松散索引扫描实现 GROUP BY 呢?实际上就是当 MySQL 完全利⽤索引扫描来实现 GROUP BY 的时候,并不需要扫描所有满⾜条件的索引键即可完成操作得出结果。
下⾯我们通过⼀个⽰例来描述松散索引扫描实现 GROUP BY,在⽰例之前我们需要⾸先调整⼀下 group_message 表的索引,将
gmt_create 字段添加到 group_id 和 user_id 字段的索引中:
sky@localhost: example 08:49:45> create index idx_gid_uid_gc
-> on group_message(group_id,user_id,gmt_create);
Query OK, rows affected (0.03 sec)
Records: 96 Duplicates: 0 Warnings: 0
sky@localhost: example 09:07:30> drop index idx_group_message_gid_uid
-> on group_message;
Query OK, 96 rows affected (0.02 sec)
Records: 96 Duplicates: 0 Warnings: 0
然后再看如下 Query 的执⾏计划:
sky@localhost: example 09:26:15> EXPLAIN
-> SELECT user_id,max(gmt_create)
-
> FROM group_message
-> WHERE group_id < 10
-> GROUP BY group_id,user_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: range
possible_keys: idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 8
ref: NULL
rows: 4
Extra: Using where; Using index for group-by
我们看到在执⾏计划的Extra 信息中有信息显⽰“Using index for group-by”,实际上这就是告诉我们,MySQL Query Optimizer 通过使⽤松散索引扫描来实现了我们所需要的 GROUP BY 操作。
下⾯这张图⽚描绘了扫描过程的⼤概实现:
要利⽤到松散索引扫描实现 GROUP BY,需要⾄少满⾜以下⼏个条件:
◆GROUP BY 条件字段必须在同⼀个索引中最前⾯的连续位置;
◆在使⽤GROUP BY 的同时,只能使⽤ MAX 和 MIN 这两个聚合函数;
◆如果引⽤到了该索引中 GROUP BY 条件之外的字段条件的时候,必须以常量形式存在;
为什么松散索引扫描的效率会很⾼?
因为在没有WHERE⼦句,也就是必须经过全索引扫描的时候,松散索引扫描需要读取的键值数量与分组的组数量⼀样多,也就是说⽐实际存在的键值数⽬要少很多。⽽在WHERE⼦句包含范围判断式或者等值表达式的时候,松散索引扫描查满⾜范围条件的每个组的第1个关键字,并且再次读取尽可能最少数量的关键字。
2.使⽤紧凑(Tight)索引扫描实现 GROUP BY
紧凑索引扫描实现 GROUP BY 和松散索引扫描的区别主要在于他需要在扫描索引的时候,读取所有满⾜条件的索引键,然后再根据读取恶的数据来完成 GROUP BY 操作得到相应结果。
group by的用法及原理详解 sky@localhost : example 08:55:14> EXPLAIN
-> SELECT max(gmt_create)
-> FROM group_message
-> WHERE group_id = 2
-> GROUP BY user_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: ref
possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: const
rows: 4
Extra: Using where; Using index
1 row in set (0.01 sec)
这时候的执⾏计划的 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 ⽆法使⽤松散索引扫描,设置⽆法直接通过索引完成 GROUP BY 操作,因为缺失的索引键信息⽆法得到。但是,如果 Query 语句中存在⼀个常量值来引⽤缺失的索引键,则可以使⽤紧凑索引扫描完成 GROUP BY 操作,因为常量填充了搜索关键字中的“差距”,可以形成完整的索引前缀。这些索引前缀可以⽤于索引查。⽽如果需要排序GROUP BY结果,并且能够形成索引前缀的搜索关键字,MySQL还可以避免额外的排序操作,因为使⽤有顺序的索引的前缀进⾏搜索已经按顺序检索到了所有关键字。
3.使⽤临时表实现 GROUP BY
MySQL 在进⾏ GROUP BY 操作的时候要想利⽤所有,必须满⾜ GROUP BY 的字段必须同时存放于同⼀个索引中,且该索引是⼀个有序索引(如 Hash 索引就不能满⾜要求)。⽽且,并不只是如此,是否能够利⽤索引来实现 GROUP BY 还与使⽤的聚合函数也有关系。
前⾯两种 GROUP BY 的实现⽅式都是在有可以利⽤的索引的时候使⽤的,当 MySQL Query Optimizer ⽆法到合适的索引可以利⽤的时候,就不得不先读取需要的数据,然后通过临时表来完成 GROUP BY 操作。
sky@localhost : example 09:02:40> EXPLAIN
-> SELECT max(gmt_create)
-> FROM group_message
-> WHERE group_id > 1 and group_id < 10
-> GROUP BY user_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: group_message
type: range
possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc
key: idx_gid_uid_gc
key_len: 4
ref: NULL
rows: 32
Extra: Using where; Using index; Using temporary; Using filesort
这次的执⾏计划⾮常明显的告诉我们 MySQL 通过索引到了我们需要的数据,然后创建了临时表,⼜进⾏了排序操作,才得到我们需要的GROUP BY 结果。整个执⾏过程⼤概如下图所展⽰:
当 MySQL Query Optimizer 发现仅仅通过索引扫描并不能直接得到 GROUP BY 的结果之后,他就不得不选择通过使⽤临时表然后再排序的⽅式来实现 GROUP BY了。
在这样⽰例中即是这样的情况。 group_id 并不是⼀个常量条件,⽽是⼀个范围,⽽且 GROUP BY 字段为 user_id。所以 MySQL ⽆法根据索引的顺序来帮助 GROUP BY 的实现,只能先通过索引范围扫描得到需要的数据,然后将数据存⼊临时表,然后再进⾏排序和分组操作来完成 GROUP BY。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论