Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

37 | 什么时候会使用内部临时表? #50

Open
git-zjx opened this issue Nov 26, 2019 · 0 comments
Open

37 | 什么时候会使用内部临时表? #50

git-zjx opened this issue Nov 26, 2019 · 0 comments
Labels
MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记

Comments

@git-zjx
Copy link
Owner

git-zjx commented Nov 26, 2019

示例表

create table t1(id int primary key, a int, b int, index(a));
delimiter ;;
create procedure idata()
begin
  declare i int;

  set i=1;
  while(i<=1000)do
    insert into t1 values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
call idata();

union 的执行

(select 1000 as f) union (select id from t1 order by id desc limit 2);

下图为该语句的 explain 结果

image

可以看到:

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

这个语句的执行流程如下:

  1. 创建一个内存临时表,这个临时表只有一个整型字段 f,并且 f 是主键字段
  2. 执行第一个子查询,得到 1000 这个值,并存入临时表中
  3. 执行第二个子查询:
    • 拿到第一行 id=1000,试图插入临时表中。但由于 1000 这个值已经存在于临时表了,违反了唯一性约束,所以插入失败,然后继续执行
    • 取到第二行 id=999,插入临时表成功
  4. 从临时表中按行取出数据,返回结果,并删除临时表,结果中包含两行数据分别是 1000 和 999

流程图如下:

image

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

如果 union 改成 union all 的话,就没有了去重的语义,这样执行的时候也就用不到临时表了,查询的结果直接作为结果集的一部分了

image

group by 的执行

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

下图为该语句的 explain 结果

image

可以看到:

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

这个语句的执行流程如下:

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

流程图如下:

image

如果不需要排序,可以使用 order by null,直接从临时表取数据返回

内存临时表的大小是有限制的,由参数 tmp_table_size 控制,默认为 16 M,如果内存临时表大小到达了上限,这时候会把内存临时表转换成磁盘临时表,磁盘临时表默认使用 InnoDB

group by 优化方法----索引

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

如果可以保证出现的数据是有序的,就可以避免临时表的使用

image

执行过程:

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

按照这个逻辑,扫描到数据结束就可以直接拿到 group by 的结果,不再需要临时表

在 MySQL 5.7 版本支持了 generated column 机制,用来实现列数据的关联更新。如果是 MySQL 5.6 及之前的版本,可以创建普通列和索引来解决这个问题

alter table t1 add column z int generated always as(id % 100), add index(z);

group by 语句可以改为:

select z, count(*) as c from t1 group by z;

explain 的结果:

image

group by 优化方法----直接排序

可以在 group by 语句中加入 SQL_BIG_RESULT 这个提示(hint),告诉优化器这个语句涉及的数据量很大,直接用磁盘临时表,这样就省去了放入内存临时表的步骤

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. 排序完成后,就得到一个有序数组
  5. 执行 group by 得到结果集

执行流程图:

image

explain 结果:

image

总结

  1. 如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果
  2. join_buffer 是无序数据,sort_buffer 是有序数组,临时表是二维表结构
  3. 如果只选逻辑需要用到二维表特性,就会优先考虑使用临时表。例如 union 需要用到唯一索引约束,group by 需要用到另外一个字段来保存累计计数
@git-zjx git-zjx added MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记 labels Nov 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记
Projects
None yet
Development

No branches or pull requests

1 participant