阿里MySQL面试题】内部临时表

网友投稿 755 2022-05-28

sort buffer、内存临时表和join buffer,都是用来存放语句执行过程中的中间数据,以辅助SQL语句的执行。在排序的时候用到了sort buffer,在使用join语句的时候用到了join buffer。

union 执行流程

示例表

创建数据

执行如下SQL:

这条语句用到了union,它的语义是,取这两个子查询结果的并集。重复的行只保留一行。

key=PRIMARY,说明第二个子句用索引id。

Extra字段,表示在对子查询的结果集做union时,使用了临时表(Using temporary)

该语句的执行流程:

创建一个内存临时表,该临时表只有一个整型字段f,并且f是主键字段

执行第一个子查询,得到1000这个值,并存入临时表中

执行第二个子查询:

拿到第一行id=1000,试图插入临时表中。但由于1000这个值已经存在于临时表了,违反了唯一性约束,所以插入失败,然后继续执行

取到第二行id=999,插入临时表成功。

从临时表中按行取出数据,返回结果,并删除临时表,结果中包含两行数据分别是1000和999。

union 执行流程

这里内存临时表用于暂存数据,而且计算过程还用上了临时表主键id的唯一性约束,实现了union语义。

若把上面语句的union改成union all,就失去了“去重”语义。执行时,就依次执行子查询,得到的结果直接作为结果集的一部分,发给客户端。也就不需要临时表了。

union all的执行计划

Extra=Using index,只使用了覆盖索引,没有用临时表。

group by 执行流程

把t1里的数据,按照 id%10 进行分组统计,并按m的结果排序后输出。

group by 的执行计划

在Extra字段里面,我们可以看到三个信息:

Using index,使用覆盖索引,选择了索引a,不需回表

Using temporary,使用临时表

Using filesort,需要排序

group by执行流程:

创建内存临时表,表里有字段m、c,主键m

扫描t1的索引a,依次取出叶子节点上的id值,计算id%10的结果,记为x;

如果临时表中没有主键为x的行,就插入一个记录(x,1)

如果表中有主键为x的行,就将x这一行的c值加1

遍历完成后,根据m排序,得到结果集返回给客户端

图中最后一步,对内存临时表的排序

内存临时表排序流程

临时表的排序过程就是图中虚线框

如果你的需求并不需要对结果进行排序,那你可以在SQL语句末尾增加order by null,也就是改成:

select id%10 as m, count(*) as c from t1 group by m order by null;

这样就跳过了最后排序,直接从临时表取数据返回:

group + order by null 的结果(内存临时表)

由于t1中的id值从1开始,因此返回的结果集中第一行是id=1;扫描到id=10的时候才插入m=0

由于临时表只有10行,内存可以放得下,因此全程只使用内存临时表。

内存临时表的大小是有限制的,参数tmp_table_size就是控制这个内存大小的,默认16M。

若执行

把内存临时表的大小限制为最大1024K,并把语句改成id % 100,这样返回结果里有100行数据。但这时内存临时表大小存不下这100行。

此时会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是InnoDB。 这时,返回的结果如图:

【阿里MySQL面试题】内部临时表

group + order by null 的结果(磁盘临时表)

若t1的数据量很大,可能该查询需要的磁盘临时表就会占用大量磁盘空间。

优化group by

索引

无论内存临时表还是磁盘临时表,group by都需要构造一个带唯一索引的表,执行代价较高。若表数据量较大,上面这个group by执行就很慢。

group by是统计不同的值出现的个数。但由于每行的id%100结果无序,所以需要有一个临时表,来记录并统计结果。

若扫描过程可保证出现的数据有序,是不是简单了?

假设,现在有一个类似如下这么一个数据结构,我们来看看group by可以怎么做。

group by算法优化-有序输入

所以,若确保输入数据有序,则计算group by时,就只需从左到右,顺序扫描,依次累加:

当碰到第一个1时,已经知道累积了X个0,结果集里的第一行就是(0,X)

当碰到第一个2的时候,已经知道累积了Y个1,结果集里的第二行就是(1,Y);

按照这个逻辑执行的话,扫描到整个输入的数据结束,即可拿到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 优化的执行计划

从 Extra 可知该语句不再需要临时表,也无需排序。

直接排序

若可以通过加索引完成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;

执行流程:

初始化sort_buffer,确定放入一个整型字段,记为m

扫描t1的索引a,依次取出里面的id值, 将 id%100值存入sort_buffer

扫描完成后,对sort_buffer的字段m做排序(如果sort_buffer内存不够用,就会利用磁盘临时文件辅助排序)

排序完成后,就得到了一个有序数组。

根据有序数组,得到数组里不同值,以及每个值的出现次数。

使用 SQL_BIG_RESULT的执行流程

SQL_BIG_RESULT的explain

该语句没有使用临时表,而直接用排序算法。

所以

MySQL什么时候会使用内部临时表?

若语句执行过程可以一边读数据,一边直接得到结果,就无需额外内存,否则就需额外内存,保存中间结果;

join_buffer是无序数组

sort_buffer是有序数组

临时表是二维表结构

若执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如我们的例子中:

union需要用到唯一索引约束

group by还需要用到另外一个字段来存累积计数

MySQL

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:GaussDB (DWS) 集群管理系列:CM组件介绍(核心功能)
下一篇:云计算入门知识:华为云IaaS全貌
相关文章