内部临时表是sql语句执行过程中,用来存储中间结果的的数据表,其作用类似于:join语句执行过程中的joinbuffer,order by语句执行过程中的sortBuffer一样。
这个表是mysql自己创建出来的,对客户端程序不可见。那么mysql什么时候会创建内部临时表呢?创建的内部临时表的表结构又是怎么样的呢?
在mysql中常见的使用临时表的场景,有两个:unoin语句和groupby语句。
为了更好的了解内部临时表在unoin和groupby中是如何起作用的,我们先了解一下unoin和groupby的执行流程。
为了方便下文的描述,我们建立如下表结构:

从以上过程中,我们可以知道,内存临时表的作用:通过唯一键约束,实现了union的语义。
如果把上述语句中的union,替换成 union all的话,那查询语句就失去了"去重"的语义了。那么,mysql在执行查询语句的过程中,是否还会使用临时表呢?
我们使用以下查询语句进行验证:

整个查询语句的执行流程如下:
1.执行第一个子查询,将查询的结果,作为结果集的一部分,返回给客户端。
2.执行第二个子查询,将查询的结果作为结果集的一部分,返回给客户端。
除了unoin查询语句在执行过程中会使用临时表外,groupby 查询语句在执行过程中,也会使用临时表。为了方便说明问题,我们执行如下查询语句:

该查询语句的执行流程如下:
1.创建临时表,表中有两个字段:m和c,其中m为主键,因为group by字段m的值,必须是唯一的。
2.扫描表t1的索引a,依次取出叶子结点上的id值,并计算id%10,将计算结果记为x,如果临时表中没有m=x的行,就插入一个记录(x,1)。如果表中有m=x数据行,那么就将x这一行的c值加1。
3.遍历完成后,在根据字段m做排序,得到最终结果返回给客户端。
对于步骤3中的排序流程,可以参考 如何优化sql中的orderBy。
通过上面的描述,我们知道了groupby的执行流程。groupby在执行过程中,需要建立一个带有唯一键索引的临时表,其中唯一键索引字段就是groupby的字段。这个执行代价还是比较高的,而且这个临时表还是一次性的。
为了提高groupby语句的执行性能,我们可以从"不使用临时表"的角度下手。首先我们可以这样想:要想让groupby的过程中不使用临时表,我们就要知道,临时表在groupby的过程中,解决了什么问题?如果,我们能找到另外一种不使用临时表,也能解决这个问题的方案,那么我们就可以不使用临时表了。
首先,我们知道,在日常开发过程中,我们使用groupby主要就是为了实现:将表中所有的数据,按照指定字段进行分组。把字段值相同的数据划分为一个组,然后在对组内的数据执行聚合函数,聚合函数计算的结果,作为结果集中的一行数据。
而在这个过程中,临时表的作用就是在扫描数据表的时候,对每行数据属于哪个组,进行记录,同时执行聚合函数的逻辑。之所以需要一个临时表来记录每行数据属于哪个组,主要是因为表中的数据,按照"group by字段"维度,不是有序的。
如果表中的数据本身就是按照"groupby字段"有序的话,也就是属于同一个组的数据都分布在一起,那么就不需要临时表,也可以对数据进行分组。 举例如下图,如果执行groupby,同时计算每组数据个数。执行流程大致如下:

1.从左到右扫描数据,并依次累加,当遇到第一个2时,说明已经积累了3个1了,此时结果集的第一行数据就是(1,3)。
2.当遇到第一个3的时候,说明已经积累了2个2了,此时结果集的第二行数据就是(2,2);
3.按照以上逻辑逐个计算,就可以得到最终结果。
在mysql中,如果分组字段上有索引的话,执行查询过程中,mysql就不会建立临时表了。
我们可以执行如下查询语句进行验证:

但是很多时候,分组字段并不是表中的一个具体字段。而是通过一定计算后的逻辑字段,如:

此时就不在使用临时表了。
上面的伴生字段的方案,需要我们向表中添加额外字段,如果业务场景比较复杂,分组的场景比较多,使用伴生字段方案需要在表中增加的额外字段就会比较多。这将会使我们的数据表结果变得比较复杂。
2.直接对分组字段进行排序
如果我们可以预估到,在执行groupby语句时,分组后的数据量比较大,使用的内存临时表可能都无法存储,那么内存临时表就会被替换成磁盘临时表,这个替换的阈值,由变量"tmp_table_size"控制,该变量的默认值为16M,如果在查询语句执行过程,需要存放到临时表中的数据量超过16M,那么使用的临时表就会变成磁盘临时表,磁盘临时表默认的存储引擎是InnoDB,磁盘临时表的性能相比内存临时表性能更低。
对于这种情况,mysql提供了 SQL_BIG_RESULT语句,该语句的作用就是告诉优化器:这个语句涉及到的数据量比较大,直接使用磁盘临时表。但是这里使用的磁盘临时表,会调整存储的数据结构,数据结构不再是B+树,而是数组。
下面我们举例说明,执行如下查询语句的的流程如下:

为了保证groupby的执行性能,在使用groupby的时候要做到以下几点:
1.尽量让 group by 过程用上表的索引,确认方法是 explain 结果里没有 Using temporary 和 Using filesort。
2.如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表。
3.如果数据量实在太大,使用 SQL_BIG_RESULT 这个提示,来告诉优化器直接使用排序算法得到 group by 的结果。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。