sort buffer、内存临时表和join buffer,都是用来存放语句执行过程中的中间数据,以辅助SQL语句的执行。在排序的时候用到了sort buffer,在使用join语句的时候用到了join buffer。
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
2 | UNION | t1 | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100 | Backward index scan; Using index |
NULL | UNION RESULT | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
这条语句用到了union,它的语义是,取这两个子查询结果的并集。重复的行只保留一行。
该语句的执行流程:
若把上面语句的union改成union all,就失去了“去重”语义。执行时,就依次执行子查询,得到的结果直接作为结果集的一部分,发给客户端。也就不需要临时表了。
把t1里的数据,按照 id%10 进行分组统计,并按m的结果排序后输出。
在Extra字段里面,我们可以看到三个信息:
图中最后一步,对内存临时表的排序
如果你的需求并不需要对结果进行排序,那你可以在SQL语句末尾增加order by null,也就是改成:
select id%10 as m, count(*) as c from t1 group by m order by null;
这样就跳过了最后排序,直接从临时表取数据返回:
由于t1中的id值从1开始,因此返回的结果集中第一行是id=1;扫描到id=10的时候才插入m=0
由于临时表只有10行,内存可以放得下,因此全程只使用内存临时表。
内存临时表的大小是有限制的,参数tmp_table_size就是控制这个内存大小的,默认16M。
若执行
把内存临时表的大小限制为最大1024K,并把语句改成id % 100
,这样返回结果里有100行数据。但这时内存临时表大小存不下这100行。
此时会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是InnoDB。 这时,返回的结果如图:
无论内存临时表还是磁盘临时表,group by都需要构造一个带唯一索引的表,执行代价较高。若表数据量较大,上面这个group by执行就很慢。
为何执行group by需要临时表?
group by是统计不同的值出现的个数。但由于每行的id%100
结果无序,所以需要有一个临时表,来记录并统计结果。
若扫描过程可保证出现的数据有序,是不是简单了?
假设,现在有一个类似如下这么一个数据结构,我们来看看group by可以怎么做。
InnoDB索引刚好满足这个输入有序。
MySQL 5.7支持generated column,以实现列数据的关联更新。
创建一个列z,然后在z创建索引(≤5.6,也可以创建普通列和索引)。
alter table t1
add column z int generated always as (id % 100),
add index (z);
这样,索引z上的数据就有序了。上面的group by即可改成:
select z, count(*) as c
from t1
group by z;
若可以通过加索引完成group by自然很棒。但若碰上不适合创建索引的场景,还是要做排序。
此时group by怎么优化?
若我们明知道,一个group by需要放到临时表上的数据量很大,却还是要“先放到内存临时表,插入一部分数据后,发现内存临时表不够用了再转成磁盘临时表”,就很蠢了
那这MySQL有无直接走磁盘临时表的方法?
有的。
在group by加入SQL_BIG_RESULT这个提示(hint),就可以告诉优化器:这个语句涉及的数据量很大,请直接用磁盘临时表。
MySQL的优化器一看,磁盘临时表是B+树存储,存储效率不如数组。所以,既然你告诉我数据量很大,那从磁盘空间考虑,还是直接用数组存。
因此,下面这个语句
select SQL_BIG_RESULT id % 100 as m, count(*) as c
from t1
group by m;
执行流程:
id%100
值存入sort_buffer根据有序数组,得到数组里不同值,以及每个值的出现次数。
所以
若语句执行过程可以一边读数据,一边直接得到结果,就无需额外内存,否则就需额外内存,保存中间结果;
若执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如我们的例子中:
如果感觉小编写得不错,请素质三连:点赞+转发+关注。我会努力写出更好的作品分享给大家。更多JAVA进阶学习资料小编已打包好