MySQL · 引擎特性 · 临时表那些事儿

前言

相比于普通的用户数据表,MySQL/InnoDB中的临时表,你们应该会陌生不少。再加上不一样的临时表建立的时机和建立的位置都不固定,这也进一步加大神秘感。最让人捉摸不透的是,临时表不少时候会先建立文件,而后什么都不作,就把文件删除,留一个句柄读写,给人的感受是神龙见首不见尾。本文分析了详细MySQL各个版本临时表的处理方式,但愿对你们有所帮助。mysql

综述

准确的说,咱们常说的临时表分为两种,一种真的是表,用来存储用户发送的数,读写走的是表读写接口,读写的时候表必定在文件系统上存在,另一种,应该是一种临时文件,用来存储SQL计算中间过程的数据,读写走的是文件读写接口,读写的时候文件可能已经被删除了,留一个文件句柄进行操做。算法

临时表

临时表能够分为磁盘临时表和内存临时表,而临时文件,只会存在于磁盘上,不会存在于内存中。具体来讲,临时表的内存形态有Memory引擎和Temptable引擎,主要区别是对字符类型(varchar, blob,text类型)的存储方式,前者无论实际字符多少,都是用定长的空间存储,后者会用变长的空间存储,这样提升了内存中的存储效率,有更多的数据能够放在内存中处理而不是转换成磁盘临时表。Memory引擎从早期的5.6就可使用,Temptable是8.0引入的新的引擎。另一方面,磁盘临时表也有三种形态,一种是MyISAM表,一种是InnoDB临时表,另一种是Temptable的文件map表。其中最后一种方式,是8.0提供的。sql

在5.6以及之前的版本,磁盘临时表都是放在数据库配置的临时目录,磁盘临时表的undolog都是与普通表的undo放在一块儿(注意因为磁盘临时表在数据库重启后就被删除了,不须要redolog经过奔溃恢复来保证事务的完整性,因此不须要写redolog,可是undolog仍是须要的,由于须要支持回滚)。数据库

在MySQL 5.7后,磁盘临时表的数据和undo都被独立出来,放在一个单独的表空间ibtmp1里面。之因此把临时表独立出来,主要是为了减小建立删除表时维护元数据的开销。缓存

在MySQL 8.0后,磁盘临时表的数据单独放在Session临时表空间池(#innodb_temp目录下的ibt文件)里面,临时表的undo放在global的表空间ibtmp1里面。另一个大的改进是,8.0的磁盘临时表数据占用的空间在链接断开后,就能释放给操做系统,而5.7的版本中须要重启才能释放。函数

目前有如下两种状况会用到临时表:性能

用户显式建立临时表

这种是用户经过显式的执行命令create temporary table建立的表,引擎的类型要么显式指定,要么使用默认配置的值(default_tmp_storage_engine)。内存使用就遵循指定引擎的内存管理方式,好比InnoDB的表会先缓存在Buffer Pool中,而后经过刷脏线程写回磁盘文件。测试

在5.6中,磁盘临时表位于tmpdir下,文件名相似#sql4d2b_8_0.ibd,其中#sql是固定的前缀,4d2b是进程号的十六进制表示,8是MySQL线程号的十六进制表示(show processlist中的id),0是每一个链接从0开始的递增值,ibd是innodb的磁盘临时表(经过参数default_tmp_storage_engine控制)。在5.6中,磁盘临时表建立好后,对应的frm以及引擎文件就在tmpdir下建立完毕,能够经过文件系统ls命令查看到。在链接关闭后,相应文件自动删除。所以,咱们若是在5.6的tmpdir里面看到不少相似格式文件名,能够经过文件名来判断是哪一个进程,哪一个链接使用的临时表,这个技巧在排查tmpdir目录占用过多空间的问题时,尤为适用。用户显式建立的这种临时表,在链接释放的时候,会自动释放并把空间释放回操做系统。临时表的undolog存在undo表空间中,与普通表的undo放在一块儿。有了undo回滚段,用户建立的这种临时表也能支持回滚了。优化

在5.7中,临时磁盘表位于ibtmp文件中,ibtmp文件位置及大小控制方式由参数innodb_temp_data_file_path控制。显式建立的表的数据和undo都在ibtmp里面。用户链接断开后,临时表会释放,可是仅仅是在ibtmp文件里面标记一下,空间是不会释放回操做系统的。若是要释放空间,须要重启数据库。另外,须要注意的一点是,5.6能够在tmpdir下直接看到建立的文件,可是5.7是建立在ibtmp这个表空间里面,所以是看不到具体的表文件的。若是须要查看,则须要查看INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO这个表,里面有一列name,这里能够看到表名。命名规格与5.6的相似,所以也能够快速找到占用空间大的链接。spa

在8.0中,临时表的数据和undo被进一步分开,数据是存放在ibt文件中(由参数innodb_temp_tablespaces_dir控制),undo依然存放在ibtmp文件中(依然由参数innodb_temp_data_file_path控制)。存放ibt文件的叫作Session临时表空间,存放undo的ibtmp叫作Global临时表空间。这里介绍一下这个存放数据的Session临时表空间。Session临时表空间,在磁盘上的表现是一组以ibt文件组成的文件池。启动的时候,数据库会在配置的目录下从新建立,关闭数据库的时候删除。启动的时候,默认会建立10个ibt文件,每一个链接最多使用两个,一个给用户建立的临时表用,另一个给下文描述的优化器建立的隐式临时表使用。固然只有在须要临时表的时候,才会建立,若是不须要,则不会占用ibt文件。当10个ibt都被使用完后,数据库会继续建立,最多建立四十万个。当链接释放时候,会自动把这个链接使用的ibt文件给释放,同时回收空间。若是要回收Global临时表空间,依然须要重启。可是因为已经把存放数据的文件分离出来,且其支持动态回收(即链接断开即释放空间),因此5.7上困扰你们多时的空间占用问题,已经获得了很好的缓解。固然,仍是有优化空间的,例如,空间须要在链接断开后,才能释放,而理论上,不少空间在某些SQL(如用户drop了某个显式建立的临时表)执行后,便可以释放。另外,若是须要查看表名,依然查看INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO这个表。须要注意的是,8.0上,显式临时表不能是压缩表,而5.6和5.7能够。

优化器隐式建立临时表

这种临时表,是数据库为了辅助某些复杂SQL的执行而建立的辅助表,是否须要临时表,通常都是由优化器决定。与用户显式建立的临时表直接建立磁盘文件不一样,若是须要优化器以为SQL须要临时表辅助,会先使用内存临时表,若是超过配置的内存(min(tmp_table_size, max_heap_table_siz)),就会转化成磁盘临时表,这种磁盘临时表就相似用户显式建立的,引擎类型经过参数internal_tmp_disk_storage_engine控制。通常稍微复杂一点的查询,包括且不限于order by, group by, distinct等,都会用到这种隐式建立的临时表。用户能够经过explain命令,在Extra列中,看是否有Using temporary这样的字样,若是有,就确定要用临时表。

在5.6中,隐式临时表依然在tmpdir下,在复杂SQL执行的过程当中,就能看到这临时表,一旦执行结束,就被删除。值得注意的是,5.6中,这种隐式建立的临时表,只能用MyISAM引擎,即没有internal_tmp_disk_storage_engine这个参数能够控制。因此,当咱们的系统中只有innodb表时,也会看到MyISAM的某些指标在变更,这种状况下,通常都是隐式临时表的缘由。

在5.7中,隐式临时表是建立在ibtmp文件中的,SQL结束后,会标记删除,可是空间依然不会返还给操做系统,若是须要返还,则须要重启数据库。另外,5.7支持参数internal_tmp_disk_storage_engine,用户能够选择InnoDB或者MYISAM表做为磁盘临时表。

在8.0中,隐式临时表是建立在Session临时表空间中的,即与用户显式建立的临时表的数据放在一块儿。若是一个链接第一次须要隐式临时表,那么数据库会从ibt文件构成的池子中取出一个给这个链接使用,直到链接释放。上文中,咱们也提到过,在8.0中,用户显式建立的临时表也会从池子中分配一个ibt来使用,每一个链接最多使用两个ibt文件用来存储临时表。咱们能够查询INFORMATION_SCHEMA.INNODB_SESSION_TEMP_TABLESPACES来肯定ibt文件的去向。这个表中,每一个ibt文件是一行,当前系统中有几个ibt文件就有几行。有一列叫作ID,若是此列为0,表示此ibt没有被使用,若是非0,表示被此ID的链接在用,好比ID为8,则表示process_id为8的链接在用这个ibt文件。另外,还有一列purpose,值为INTRINSIC表示是隐式临时表在用这个ibt,USER则表示是显示临时表在用。此外,还有一列size,表示当前的大小。用户能够查询这个表来肯定整个数据库临时表的使用状况,十分方便。

在5.6和5.7中,内存临时表只能使用Memory引擎,到了8.0,多了一种Temptable引擎的选择。Temptable在存储格式有采用了变长存储,能够节省存储空间,进一步提升内存使用率,减小转换成磁盘临时表的次数。若是设置的磁盘临时表是InnoDB或者MYISAM,则须要一个转换拷贝的消耗。为了尽量减小消耗,Temptable提出了一种overflow机制,即若是内存临时表超过配置大小,则使用磁盘空间map的方式,即打开一个文件,而后删除,留一个句柄进行读写操做。读写文件格式和内存中格式同样,这样就略过了转换这一步,进一步提升性能。注意,这个功能是在还没发布的8.0.16版本中才有的,由于还看不到代码,只能经过文档猜想其实现。在8.0.16中,参数internal_tmp_disk_storage_engine已经被去掉,磁盘临时表只能使用InnoDB形式或者TempTable的这种overflow形式。从文档中,咱们彷佛看出官方比较推荐使用TempTable这个新的引擎。具体性能提高状况,还须要等代码发布后,测试过才能得出结论。

临时文件

相比临时表,临时文件对你们可能更加陌生,临时文件更多的被使用在缓存数据,排序数据的场景中。通常状况下,被缓存或者排序的数据,首先放在内存中,若是内存放不下,才会使用磁盘临时文件的方式。临时文件的使用方式与通常的表也不太同样,通常的表建立完后,就开始读写数据,使用完后,才把文件删除,可是临时文件的使用方式不同,在建立完后(使用mkstemp系统函数),立刻调用unlink删除文件,可是不close文件,后续使用原来的句柄操做文件。这样的好处是,当进程异常crash,不会有临时文件由于没被删除而残留,可是坏处也是明显的,咱们在文件系统上使用ls命令就看不到这个文件,须要使用lsof +L1来查看这种deleted属性的文件。

目前,咱们主要在一下场景使用临时文件:

DDL中的临时文件

在作online DDL的过程当中,不少操做须要对原表进行重建,对表重建前,须要对各类二级索引排序,而大量数据的排序,不太可能在内存中完成,须要依赖外部排序算法,MySQL使用了归并排序。这个过程当中就须要建立临时文件。通常须要的空间大小与原表差很少。可是在使用完以后,会立刻清理,因此在作DDL的时候,须要保留出足够的空间。用户能够经过指定innodb_tmpdir来指定这种排序文件的路径。这个参数能够动态修改,通常把他设置在有足够磁盘空间的路径上。临时文件的名字通常是相似ibXXXXXX,其中ib是固定前缀,XXXXXX是大小写字母以及数字的随机组合。

在作online DDL中,咱们是容许用户对原表作DML操做的,即增删改查。咱们不能直接插入原表中,所以须要一个地方记录对原表的修改操做,在DDL结束后,再应用在新表上。这个记录的地方就是online log,固然若是改动少的话,直接存在内存里(参数innodb_sort_buffer_size可控制,同时这个参数也控制online log每一个读写块的大小)面便可。这个onlinelog也是用临时文件存,建立在innodb_tmpdir,最大大小为参数innodb_online_alter_log_max_size控制,若是超过这个大小了,DDL就会失败。临时文件的名字也相似上述的排序临时文件的名字。

在online DDL的最后阶段,须要把排序完的文件和中途产生的DML全都应用到一个中间文件上,中间文件文件名相似#sql-ib53-522550444.ibd,其中#sql-ib是固定的前缀,53是InnoDB层的table id,522550444是随机生成的数字。同时,在server层也会生成一个frm文件(8.0中没有),文件名相似#sql-4d2b_2a.frm,其中#sql是固定前缀,4d2b是进程号的十六进制表示,2a是线程号的十六进制表示(show processlist中的id)。所以咱们也能够经过这个命名规则来找到哪一个线程在作DDL。这里须要注意一点,这里说的中间文件,其实算是一个临时表,并非上文说中临时文件,这些中间文件能够经过ls来查看。当在DDL中的最后一步,会把这两个临时文件命名回原来的表名。正由于这个特性,因此当数据库中途crash的时候,可能会在磁盘上留下残余无用的文件。遇到这种状况,能够先把frm文件重命名成与ibd文件同样的名字,而后使用DROP TABLE#mysql50##sql-ib53-522550444`来清理残余的文件。注意,若是不用drop命令,直接删除ibd文件,可能会致使数据字典里面依然有残余的信息,作法不太优雅。固然,在8.0中,因为使用了原子的数据字典,就不会出现这种残余文件了。

BinLog中的缓存操做

BinLog只有在事务提交的时候才会写入到文件中,在没提交前,会先放在内存中(由参数binlog_cache_size控制),若是内存放慢了,就会建立临时文件,使用方法也是先经过mkstemp建立,而后直接unlink,留一个句柄读写。临时文件名相似MLXXXXXX,其中ML是固定前缀,XXXXXX是大小写字母以及数字的随机组合。单个事务的BinLog太大,可能会致使整个BinLog的大小也过大,从而影响同步,所以咱们须要尽量控制事务大小。

优化建立的临时文件

有些操做,除了在引擎层须要依赖隐式临时表来辅助复杂SQL的计算,在Server层,也会建立临时文件来辅助,好比order by操做,会调用filesort函数。这个函数也会先使用内存(sort_buffer_size)排序,若是不够,就会建立一个临时文件,辅助排序。文件名相似MYXXXXXX,其中MY是固定前缀,XXXXXX是大小写字母以及数字的随机组合。

Load data中用的临时文件

在BinLog复制中,若是在主库上使用了Load Data命令,即从文件中导数据,数据库会把整个文件写入到RelayLog中,而后传到备库,备库解析RelayLog,从中抽取出对应的Load文件,而后在备库上应用。备库上这个文件存储的位置由参数slave_load_tmpdir控制。文档中建议这个目录不要配置在物理机的内存目录或者重启后会删除的目录。由于复制依赖这个文件,若是意外被删除,会致使复制中断。

其余

除了上文所述的几个地方外,还有其余几个地方也会用到临时文件:

  • 在InnoDB层,启动的时候会建立多个临时文件用来存储:最后一次外键或者惟一键错误; 最后一次死锁的信息; 最后的innodb状态信息。用临时文件而不用内存的缘由猜想是,内存使用率不会由于写这些指标而波动。
  • 在Server层,分区表使用show create table时,会用到临时文件。另外在MYISAM表内部排序的时候也会用到临时文件。

相关参数

*** tmpdir: *** 这个参数是临时目录的配置,在5.6以及以前的版本,临时表/文件默认都会放在这里。这个参数能够配置多个目录,这样就能够轮流在不一样的目录上建立临时表/文件,若是不一样的目录分别指向不一样的磁盘,就能够达到分流的目的。
*** innodb_tmpdir: *** 这个参数只要是被DDL中的排序临时文件使用的。其占用的空间会很大,建议单独配置。这个参数能够动态设置,也是一个Session变量。
*** slave_load_tmpdir: *** 这个参数主要是给BinLog复制中Load Data时,配置备库存放临时文件位置时使用。由于数据库Crash后还须要依赖Load数据的文件,建议不要配置重启后会删除数据的目录。
*** internal_tmp_disk_storage_engine: *** 当隐式临时表被转换成磁盘临时表时,使用哪一种引擎,默认只有MyISAM和InnoDB。5.7及之后的版本才支持。8.0.16版本后取消的这个参数。
*** internal_tmp_mem_storage_engine: *** 隐式临时表在内存时用的存储引擎,能够选择Memory或者Temptable引擎。建议选择新的Temptable引擎。
*** default_tmp_storage_engine: *** 默认的显式临时表的引擎,即用户经过SQL语句建立的临时表的引擎。
*** tmp_table_size: *** min(tmp_table_size,max_heap_table_size)是隐式临时表的内存大小,超过这个值会转换成磁盘临时表。
*** max_heap_table_size: *** 用户建立的Memory内存表的内存限制大小。
*** big_tables: *** 内存临时表转换成磁盘临时表须要有个转化操做,须要在不一样引擎格式中转换,这个是须要消耗的。若是咱们能提早知道执行某个SQL须要用到磁盘临时表,即内存确定不够用,能够设置这个参数,这样优化器就跳过使用内存临时表,直接使用磁盘临时表,减小开销。
*** temptable_max_ram: *** 这个参数是8.0后才有的,主要是给Temptable引擎指定内存大小,超过这个后,要么就转换成磁盘临时表,要么就使用自带的overflow机制。
*** temptable_use_mmap: *** 是否使用Temptable的overflow机制。

总结建议

MySQL的临时表以及临时文件实际上是一个比较复杂的话题,涉及的模块比较多,出现的时机比较难把握,致使排查问题相比普通表也难很多。建议读者结合代码细细研究,这样才能定位在线上可能出现的棘手问题。