3

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

 2 years ago
source link: https://blog.51cto.com/u_11440114/5146268
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

union 执行流程

  • 示例表
    【阿里MySQL面试题】内部临时表_sql

  • 创建数据
    【阿里MySQL面试题】内部临时表_数据_02
    执行如下SQL:
    【阿里MySQL面试题】内部临时表_sql_03

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 <union1,2> NULL ALL NULL NULL NULL NULL NULL NULL Using temporary

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

  • key=PRIMARY,说明第二个子句用索引id。
  • Extra字段,表示在对子查询的结果集做union时,使用了临时表(Using temporary)

该语句的执行流程:

  1. 创建一个内存临时表,该临时表只有一个整型字段f,并且f是主键字段
  2. 执行第一个子查询,得到1000这个值,并存入临时表中
  3. 执行第二个子查询:
    • 拿到第一行id=1000,试图插入临时表中。但由于1000这个值已经存在于临时表了,违反了唯一性约束,所以插入失败,然后继续执行
    • 取到第二行id=999,插入临时表成功。
  4. 从临时表中按行取出数据,返回结果,并删除临时表,结果中包含两行数据分别是1000和999。
  • union 执行流程
    【阿里MySQL面试题】内部临时表_数据_04
    这里内存临时表用于暂存数据,而且计算过程还用上了临时表主键id的唯一性约束,实现了union语义。

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

  • union all的执行计划
    【阿里MySQL面试题】内部临时表_3c_05
    Extra=Using index,只使用了覆盖索引,没有用临时表。

group by 执行流程

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

  • group by 的执行计划
    【阿里MySQL面试题】内部临时表_sql_06

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

  • Using index,使用覆盖索引,选择了索引a,不需回表
  • Using temporary,使用临时表
  • Using filesort,需要排序

group by执行流程:
【阿里MySQL面试题】内部临时表_sql_07

  1. 创建内存临时表,表里有字段m、c,主键m
  2. 扫描t1的索引a,依次取出叶子节点上的id值,计算id%10的结果,记为x;
    • 如果临时表中没有主键为x的行,就插入一个记录(x,1)
    • 如果表中有主键为x的行,就将x这一行的c值加1
  3. 遍历完成后,根据m排序,得到结果集返回给客户端

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

  • 内存临时表排序流程
    【阿里MySQL面试题】内部临时表_3c_08
    临时表的排序过程就是图中虚线框

如果你的需求并不需要对结果进行排序,那你可以在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。

若执行
【阿里MySQL面试题】内部临时表_数据_09
把内存临时表的大小限制为最大1024K,并把语句改成id % 100,这样返回结果里有100行数据。但这时内存临时表大小存不下这100行。
此时会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是InnoDB。 这时,返回的结果如图:

  • group + order by null 的结果(磁盘临时表)
    【阿里MySQL面试题】内部临时表_sql_10
    若t1的数据量很大,可能该查询需要的磁盘临时表就会占用大量磁盘空间。

优化group by

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

为何执行group by需要临时表?

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

若扫描过程可保证出现的数据有序,是不是简单了?
假设,现在有一个类似如下这么一个数据结构,我们来看看group by可以怎么做。

  • group by算法优化-有序输入
    【阿里MySQL面试题】内部临时表_数据_11
    所以,若确保输入数据有序,则计算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 优化的执行计划
    【阿里MySQL面试题】内部临时表_sql_12
    从 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;

执行流程:

  1. 初始化sort_buffer,确定放入一个整型字段,记为m
  2. 扫描t1的索引a,依次取出里面的id值, 将 id%100值存入sort_buffer
  3. 扫描完成后,对sort_buffer的字段m做排序(如果sort_buffer内存不够用,就会利用磁盘临时文件辅助排序)
  4. 排序完成后,就得到了一个有序数组。

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

  • 使用 SQL_BIG_RESULT的执行流程
    【阿里MySQL面试题】内部临时表_sql_13
  • SQL_BIG_RESULT的explain
    【阿里MySQL面试题】内部临时表_3c_14
    该语句没有使用临时表,而直接用排序算法。

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

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

  • join_buffer是无序数组
  • sort_buffer是有序数组
  • 临时表是二维表结构

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

  • union需要用到唯一索引约束
  • group by还需要用到另外一个字段来存累积计数

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK