如果 SQL 在执行过程中读到的数据无法直接得到结果,那么就需要额外的内存来保存中间结果,得出最终结果,这个额外的内存就是内部临时表。比如 group by 执行时,就需要构建一个临时表,需要额外的字段保存聚合函数的结果,当然为了防止内存使用过大,一般超出某个限制后就会放到磁盘上。关于哪些操作会产生内部临时表,可以查看官方文档:https://dev.mysql.com/doc/refman/8.0/en/internal-temporary-tables.html,下面主要介绍 MySQL 8.0 内部临时表存放方式的变化。
MySQL 5.6 中,内部临时表大小超过内存限制后是在临时目录创建的,每个临时表有自己的表空间文件,当 SQL 执行完会删除内部临时表,对应临时目录中的文件也会删除。如果禁用了 innodb_file_per_table ,则在数据目录中的 InnoDB 共享表空间(ibdata1)中创建,即使 SQL 执行完也不会释放释放这部分空间,很容易造成 ibdata1 过大,只能逻辑导出数据迁移到新实例解决。
MySQL 5.7 在临时表空间上做了改进,已经实现将临时表空间从 InnoDB 共享表空间或者独立表空间中分离,现在叫共享临时表空间。好处有二:
其表现是 MySQL 启动时 datadir 下会创建一个 ibtmp1 文件,默认值下会无限扩展。例如,如果某个 SQL 执行时创建了一个大小为 20MB 的内部磁盘临时表,则创建时默认大小为 12MB 的临时表空间文件会扩展到 20MB 以适应该表。当 SQL 执行完删除临时表时,释放的空间可以重新用于新的临时表,但 ibtmp1 文件保持扩展大小,只有重启 MySQL 时才会真正回收共享临时表空间变成初始大小 12MB。
相关参数:
ibtmp1:12M:autoextend
,即 datadir/ibtmp1,初始大小12M可以无限扩展,建议限制一个最大值防止把磁盘撑满。缺点:SQL 执行完产生的内部临时表可能很大,必须要重启才能释放。这点曾一度让我很困惑,为什么不能做的更好一点执行完就释放呢?所幸 MySQL8.0 优化了这个问题。
MySQL 8.0又有较大变化,新增了一些参数:
也就是说,默认情况下执行 SQL 产生内部临时表,使用的存储引擎从 MEMORY 变成了 TempTable,当然 TempTable 依然是一种内存表,可以使用的最大内存是1G(默认)。当大小超过1G,会使用内存映射临时文件作为内部临时表的溢出机制
,大白话就是防止内存使用太大,把内存中的数据放在临时文件中。
但是你想想,关系型数据库设计了存储引擎这么好的东西来存放数据,这时候用文件来存是不是过分了点?估计官方是这么想的:哎呀内部临时表很小的,我就临时放放,你忍忍。后来发现有些内部临时表太大了忍不了,为了防止内存映射临时文件过大,8.0.23版本引入一个新参数 temptable_max_mmap 来限制其大小,如果超过其大小(默认1G),则转化为磁盘临时表(这点和 MySQL 5.7一致)。值得注意的是 temptable_use_mmap 参数 8.0.26 标记被弃用了,官方文档也提示建议设置为0将其关闭,所以个人理解使用内存映射临时文件作为内部临时表的溢出机制
是一个糟糕的方案。
为什么要把内部临时表默认引擎换成 TempTable ?它与 MEMORY 最大的不同是:
那么真的那么好用吗?目前最新版本是8.0.26,还是存在一些问题的,例如:https://bugs.mysql.com/bug.php?id=98782
https://bugs.mysql.com/bug.php?id=98739
https://bugs.mysql.com/bug.php?id=99593
https://bugs.mysql.com/bug.php?id=99100
前3个都是性能问题,后面一个则可能会导致 SQL 执行时报错:The table '/tmp/#sql639b7_13_4' is full,所以在这些问题解决前,建议设置internal_tmp_mem_storage_engine=MEMORY
。
MySQL 8.0 临时表空间也发生了变化,分为了会话临时表空间
和全局临时表空间
内,全局临时表空间内和 MySQL 5.7 时没什么两样,不过 SQL 产生的内部临时表将存储在会话临时表空间
中。
新参数:
shell> ls datadir/#innodb_temp
temp_10.ibt temp_2.ibt temp_4.ibt temp_6.ibt temp_8.ibt
temp_1.ibt temp_3.ibt temp_5.ibt temp_7.ibt temp_9.ibt
会话临时表空间其实是个包含10个临时表空间的池,会话临时表空间在第一次请求创建磁盘临时表时从临时表空间池中分配给会话。一个会话最多分配两个表空间,一个用于用户创建的临时表,另一个用于优化器创建的内部临时表。当会话断开连接时,其临时表空间被清除并释放回池中。
temptable_use_mmap = ON 时,如果内部临时表超过了 temptable_max_ram 大小,使用内存映射的临时文件用作内部临时表的溢出机制,临时文件放在 tmpdir 目录下:
可以看到临时文件数量+1,磁盘临时表数量不变:
temptable_use_mmap = OFF 时,如果内部临时表超过了temptable_max_ram 大小,使用 InnoDB 磁盘内部临时表用作内部临时表的溢出机制,存放在 innodb 会话临时表空间中,与 MySQL 5.7 的区别是,session 断开后就会释放空间,不需要重启 MySQL :
可以看到临时文件数量不变,磁盘临时表数量+1: