【mysql45讲】什么时候会使用内部临时表union&group by

1.什么时候会使用内部临时表

1.1 union

举例:
(select 1000 as f) union (select id from t1 order by id desc limit 2);
【mysql45讲】什么时候会使用内部临时表union&group by
这里的内存临时表起到了暂存数据的作用,而且计算过程还用上了临时表主键 id 的唯一性约束,实现了 union 的语义。
如果把上面这个语句中的 union 改成 union all 的话,就没有了“去重”的语义。这样执行的时候,就依次执行子查询,得到的结果直接作为结果集的一部分,发给客户端。因此也就不需要临时表了。

1.2 group by

1.2.1举例:

select id%10 as m, count(*) as c from t1 group by m;
这个语句的逻辑是把表 t1 里的数据,按照 id%10 进行分组统计,并按照 m 的结果排序后输出。
【mysql45讲】什么时候会使用内部临时表union&group by
如果你的需求并不需要对结果进行排序,那你可以在 SQL 语句末尾增加 order by null。这样就跳过了最后排序的阶段,直接从临时表中取数据返回。

1.2.2 对内存临时表的排序(来自16讲)

需要排序时,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer。

1.2.2.1 全字段排序

举例
select city,name,age from t where city=‘杭州’ order by name limit 1000 ;
【mysql45讲】什么时候会使用内部临时表union&group by
“按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。

可以利用OPTIMIZER_TRACE 的结果来确认,你可以从 number_of_tmp_files 中看到是否使用了临时文件。(具体就不写了)

1.2.2.2 rowid 排序

在上面这个算法过程里面,只对原表的数据读了一遍,剩下的操作都是在 sort_buffer 和临时文件中执行的。但这个算法有一个问题,就是如果查询要返回的字段很多的话,那么 sort_buffer 里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。所以如果单行很大,这个方法效率不够好。

举例SET max_length_for_sort_data = 16;
max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。

【mysql45讲】什么时候会使用内部临时表union&group by
rowid 排序多访问了一次表 t 的主键索引。
需要说明的是,最后的“结果集”是一个逻辑概念,实际上 MySQL 服务端从排序后的 sort_buffer 中依次取出 id,然后到原表查到 city、name 和 age 这三个字段的结果,不需要在服务端再耗费内存存储结果,是直接返回给客户端的。

1.2.2.3 全字段排序和rowid排序

对于 InnoDB 表来说,rowid 排序会要求回表多造成磁盘读,因此不会被优先选择。

1.2.3 内存临时表的大小

内存临时表的大小是有限制的,参数 tmp_table_size 就是控制这个内存大小的,默认是 16M。如果达到上限,就会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是 InnoDB。

1.2.4 group by 优化方法

如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表;

1.2.4.1 索引

如果可以确保输入的数据是有序的,那么计算 group by 的时候,就只需要从左到右,顺序扫描,依次累加。
改成

alter table t1 add column z int generated always as(id % 100), add
index(z); select z, count(*) as c from t1 group by z;

这样就不再需要临时表,不再需要排序了。

1.2.4.2 直接排序

在 group by 语句中加入 SQL_BIG_RESULT 这个提示(hint),就可以告诉优化器:这个语句涉及的数据量很大,请直接用磁盘临时表。

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

【mysql45讲】什么时候会使用内部临时表union&group by

1.3 总结-什么时候需要临时表

如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果;
join_buffer 是无序数组,sort_buffer 是有序数组,临时表是二维表结构;
如果执行逻辑需要用到二维表特性,就会优先考虑使用临时表。比如我们的例子中,union 需要用到唯一索引约束, group by 还需要用到另外一个字段来存累积计数。