GROUP BY 优化之临时表
MySQL 在进行 GROUP BY 操作时要想利用索引,必须满足 GROUP BY 的字段同时存放于同一个索引中,且该索引是一个有序索引(如 Hash 索引就不能满足要求)。不仅如此,是否能够利用索引来实现 GROUP BY 还与使用的聚合函数有关系。
前面两种 GROUP BY 的实现方式都是在有可以利用的索引时使用的,当 MySQL Query Optimizer无法找到可以利用的合适索引时,就不得不先读取需要的数据,然后通过临时表来完成 GROUP BY 操作,如示例代码8-27所示:
代码8-27
- 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 结果。整个执行过程大概如图8-9所示:
![]() |
(点击查看大图)图8-9 |
当 MySQL Query Optimizer 发现仅通过索引扫描并不能直接得到 GROUP BY 的结果时,它就不得不选择使用临时表,然后通过再排序的方式来实现 GROUP BY了。
本示例是这样的情况。group_id 并不是一个常量条件,而是一个范围,而且 GROUP BY 字段为 user_id。所以 MySQL 无法根据索引的顺序来帮助 GROUP BY 的实现,只能先通过索引范围扫描得到需要的数据,将数据存入临时表,然后再进行排序和分组操作来完成 GROUP BY。
对于上面三种 MySQL 处理 GROUP BY 的方式,可以针对性地得出如下两种优化思路:
(1)尽可能让 MySQL利用索引来完成 GROUP BY 操作,当然最好是松散索引扫描的方式。在系统允许的情况下,可以通过调整索引或调整 Query 这两种方式来达到目的;
(2)当无法使用索引完成 GROUP BY时,由于要使用到临时表且需要 filesort,所以必须要有足够的 sort_buffer_size供 MySQL排序时使用,而且尽量不要进行大结果集的 GROUP BY 操作,因为如果超出系统设置的临时表大小就会出现将临时表数据复制(copy)到磁盘上面再进行操作的情况,这时的排序分组操作性能将成数量级的下降。
至于如何利用好这两种思路,还须要大家在实际应用场景中不断地尝试并测试效果,才能最终得到较佳方案。此外,在优化 GROUP BY时还有一个小技巧可以让我们在有些无法利用到索引的情况下避免 filesort 操作,即在整个语句最后添加一个以 null 排序(ORDER BY null)的子句,大家可以尝试一下看会有什么效果。