learn.lianglianglee.com/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html
2022-05-11 19:04:14 +08:00

967 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>MySQL · 引擎特性 · 临时表那些事儿.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI.md.html</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>MySQL · 引擎特性 · 临时表那些事儿</h1>
<h2>前言</h2>
<p>相比于普通的用户数据表MySQL/InnoDB中的临时表大家应该会陌生很多。再加上不同的临时表创建的时机和创建的位置都不固定这也进一步加大神秘感。最让人捉摸不透的是临时表很多时候会先创建文件然后什么都不做就把文件删除留一个句柄读写给人的感觉是神龙见首不见尾。本文分析了详细MySQL各个版本临时表的处理方式希望对大家有所帮助。</p>
<h2>综述</h2>
<p>准确的说我们常说的临时表分为两种一种真的是表用来存储用户发送的数读写走的是表读写接口读写的时候表一定在文件系统上存在另外一种应该是一种临时文件用来存储SQL计算中间过程的数据读写走的是文件读写接口读写的时候文件可能已经被删除了留一个文件句柄进行操作。</p>
<h3>临时表</h3>
<p>临时表可以分为磁盘临时表和内存临时表而临时文件只会存在于磁盘上不会存在于内存中。具体来说临时表的内存形态有Memory引擎和Temptable引擎主要区别是对字符类型(varchar, blobtext类型)的存储方式前者不管实际字符多少都是用定长的空间存储后者会用变长的空间存储这样提高了内存中的存储效率有更多的数据可以放在内存中处理而不是转换成磁盘临时表。Memory引擎从早期的5.6就可以使用Temptable是8.0引入的新的引擎。另外一方面磁盘临时表也有三种形态一种是MyISAM表一种是InnoDB临时表另外一种是Temptable的文件map表。其中最后一种方式是8.0提供的。</p>
<p>在5.6以及以前的版本磁盘临时表都是放在数据库配置的临时目录磁盘临时表的undolog都是与普通表的undo放在一起(注意由于磁盘临时表在数据库重启后就被删除了不需要redolog通过奔溃恢复来保证事务的完整性所以不需要写redolog但是undolog还是需要的因为需要支持回滚)。</p>
<p>在MySQL 5.7后磁盘临时表的数据和undo都被独立出来放在一个单独的表空间ibtmp1里面。之所以把临时表独立出来主要是为了减少创建删除表时维护元数据的开销。</p>
<p>在MySQL 8.0后磁盘临时表的数据单独放在Session临时表空间池(#innodb_temp目录下的ibt文件)里面临时表的undo放在global的表空间ibtmp1里面。另外一个大的改进是8.0的磁盘临时表数据占用的空间在连接断开后就能释放给操作系统而5.7的版本中需要重启才能释放。</p>
<p>目前有以下两种情况会用到临时表:</p>
<h4>用户显式创建临时表</h4>
<p>这种是用户通过显式的执行命令<code>create temporary table</code>创建的表,引擎的类型要么显式指定,要么使用默认配置的值(default_tmp_storage_engine)。内存使用就遵循指定引擎的内存管理方式比如InnoDB的表会先缓存在Buffer Pool中然后通过刷脏线程写回磁盘文件。</p>
<p>在5.6中磁盘临时表位于tmpdir下文件名类似<code>#sql4d2b_8_0.ibd</code>,其中<code>#sql</code>是固定的前缀,<code>4d2b</code>是进程号的十六进制表示,<code>8</code>是MySQL线程号的十六进制表示(show processlist中的id)<code>0</code>是每个连接从0开始的递增值<code>ibd</code>是innodb的磁盘临时表(通过参数<code>default_tmp_storage_engine</code>控制)。在5.6中磁盘临时表创建好后对应的frm以及引擎文件就在tmpdir下创建完毕可以通过文件系统ls命令查看到。在连接关闭后相应文件自动删除。因此我们如果在5.6的tmpdir里面看到很多类似格式文件名可以通过文件名来判断是哪个进程哪个连接使用的临时表这个技巧在排查tmpdir目录占用过多空间的问题时尤其适用。用户显式创建的这种临时表在连接释放的时候会自动释放并把空间释放回操作系统。临时表的undolog存在undo表空间中与普通表的undo放在一起。有了undo回滚段用户创建的这种临时表也能支持回滚了。</p>
<p>在5.7中临时磁盘表位于ibtmp文件中ibtmp文件位置及大小控制方式由参数<code>innodb_temp_data_file_path</code>控制。显式创建的表的数据和undo都在ibtmp里面。用户连接断开后临时表会释放但是仅仅是在ibtmp文件里面标记一下空间是不会释放回操作系统的。如果要释放空间需要重启数据库。另外需要注意的一点是5.6可以在tmpdir下直接看到创建的文件但是5.7是创建在ibtmp这个表空间里面因此是看不到具体的表文件的。如果需要查看则需要查看<code>INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO</code>这个表里面有一列name这里可以看到表名。命名规格与5.6的类似,因此也可以快速找到占用空间大的连接。</p>
<p>在8.0中临时表的数据和undo被进一步分开数据是存放在ibt文件中(由参数<code>innodb_temp_tablespaces_dir</code>控制)undo依然存放在ibtmp文件中(依然由参数<code>innodb_temp_data_file_path</code>控制)。存放ibt文件的叫做Session临时表空间存放undo的ibtmp叫做Global临时表空间。这里介绍一下这个存放数据的Session临时表空间。Session临时表空间在磁盘上的表现是一组以ibt文件组成的文件池。启动的时候数据库会在配置的目录下重新创建关闭数据库的时候删除。启动的时候默认会创建10个ibt文件每个连接最多使用两个一个给用户创建的临时表用另外一个给下文描述的优化器创建的隐式临时表使用。当然只有在需要临时表的时候才会创建如果不需要则不会占用ibt文件。当10个ibt都被使用完后数据库会继续创建最多创建四十万个。当连接释放时候会自动把这个连接使用的ibt文件给释放同时回收空间。如果要回收Global临时表空间依然需要重启。但是由于已经把存放数据的文件分离出来且其支持动态回收(即连接断开即释放空间)所以5.7上困扰大家多时的空间占用问题已经得到了很好的缓解。当然还是有优化空间的例如空间需要在连接断开后才能释放而理论上很多空间在某些SQL(如用户drop了某个显式创建的临时表)执行后,即可以释放。另外,如果需要查看表名,依然查看<code>INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO</code>这个表。需要注意的是8.0上显式临时表不能是压缩表而5.6和5.7可以。</p>
<h4>优化器隐式创建临时表</h4>
<p>这种临时表是数据库为了辅助某些复杂SQL的执行而创建的辅助表是否需要临时表一般都是由优化器决定。与用户显式创建的临时表直接创建磁盘文件不同如果需要优化器觉得SQL需要临时表辅助会先使用内存临时表如果超过配置的内存(min(tmp_table_size, max_heap_table_siz)),就会转化成磁盘临时表,这种磁盘临时表就类似用户显式创建的,引擎类型通过参数<code>internal_tmp_disk_storage_engine</code>控制。一般稍微复杂一点的查询包括且不限于order by, group by, distinct等都会用到这种隐式创建的临时表。用户可以通过explain命令在Extra列中看是否有Using temporary这样的字样如果有就肯定要用临时表。</p>
<p>在5.6中隐式临时表依然在tmpdir下在复杂SQL执行的过程中就能看到这临时表一旦执行结束就被删除。值得注意的是5.6中这种隐式创建的临时表只能用MyISAM引擎即没有<code>internal_tmp_disk_storage_engine</code>这个参数可以控制。所以当我们的系统中只有innodb表时也会看到MyISAM的某些指标在变动这种情况下一般都是隐式临时表的原因。</p>
<p>在5.7中隐式临时表是创建在ibtmp文件中的SQL结束后会标记删除但是空间依然不会返还给操作系统如果需要返还则需要重启数据库。另外5.7支持参数<code>internal_tmp_disk_storage_engine</code>用户可以选择InnoDB或者MYISAM表作为磁盘临时表。</p>
<p>在8.0中隐式临时表是创建在Session临时表空间中的即与用户显式创建的临时表的数据放在一起。如果一个连接第一次需要隐式临时表那么数据库会从ibt文件构成的池子中取出一个给这个连接使用直到连接释放。上文中我们也提到过在8.0中用户显式创建的临时表也会从池子中分配一个ibt来使用每个连接最多使用两个ibt文件用来存储临时表。我们可以查询<code>INFORMATION_SCHEMA.INNODB_SESSION_TEMP_TABLESPACES</code>来确定ibt文件的去向。这个表中每个ibt文件是一行当前系统中有几个ibt文件就有几行。有一列叫做ID如果此列为0表示此ibt没有被使用如果非0表示被此ID的连接在用比如ID为8则表示process_id为8的连接在用这个ibt文件。另外还有一列purpose值为INTRINSIC表示是隐式临时表在用这个ibtUSER则表示是显示临时表在用。此外还有一列size表示当前的大小。用户可以查询这个表来确定整个数据库临时表的使用情况十分方便。</p>
<p>在5.6和5.7中内存临时表只能使用Memory引擎到了8.0多了一种Temptable引擎的选择。Temptable在存储格式有采用了变长存储可以节省存储空间进一步提高内存使用率减少转换成磁盘临时表的次数。如果设置的磁盘临时表是InnoDB或者MYISAM则需要一个转换拷贝的消耗。为了尽可能减少消耗Temptable提出了一种overflow机制即如果内存临时表超过配置大小则使用磁盘空间map的方式即打开一个文件然后删除留一个句柄进行读写操作。读写文件格式和内存中格式一样这样就略过了转换这一步进一步提高性能。注意这个功能是在还没发布的8.0.16版本中才有的因为还看不到代码只能通过文档猜测其实现。在8.0.16中,参数<code>internal_tmp_disk_storage_engine</code>已经被去掉磁盘临时表只能使用InnoDB形式或者TempTable的这种overflow形式。从文档中我们似乎看出官方比较推荐使用TempTable这个新的引擎。具体性能提升情况还需要等代码发布后测试过才能得出结论。</p>
<h3>临时文件</h3>
<p>相比临时表,临时文件对大家可能更加陌生,临时文件更多的被使用在缓存数据,排序数据的场景中。一般情况下,被缓存或者排序的数据,首先放在内存中,如果内存放不下,才会使用磁盘临时文件的方式。临时文件的使用方式与一般的表也不太一样,一般的表创建完后,就开始读写数据,使用完后,才把文件删除,但是临时文件的使用方式不一样,在创建完后(使用mkstemp系统函数)马上调用unlink删除文件但是不close文件后续使用原来的句柄操作文件。这样的好处是当进程异常crash不会有临时文件因为没被删除而残留但是坏处也是明显的我们在文件系统上使用ls命令就看不到这个文件需要使用lsof +L1来查看这种deleted属性的文件。</p>
<p>目前,我们主要在一下场景使用临时文件:</p>
<h4>DDL中的临时文件</h4>
<p>在做online DDL的过程中很多操作需要对原表进行重建对表重建前需要对各种二级索引排序而大量数据的排序不太可能在内存中完成需要依赖外部排序算法MySQL使用了归并排序。这个过程中就需要创建临时文件。一般需要的空间大小与原表差不多。但是在使用完之后会马上清理所以在做DDL的时候需要保留出足够的空间。用户可以通过指定innodb_tmpdir来指定这种排序文件的路径。这个参数可以动态修改一般把他设置在有足够磁盘空间的路径上。临时文件的名字一般是类似<code>ibXXXXXX</code>,其中<code>ib</code>是固定前缀,<code>XXXXXX</code>是大小写字母以及数字的随机组合。</p>
<p>在做online DDL中我们是允许用户对原表做DML操作的即增删改查。我们不能直接插入原表中因此需要一个地方记录对原表的修改操作在DDL结束后再应用在新表上。这个记录的地方就是online log当然如果改动少的话直接存在内存里(参数<code>innodb_sort_buffer_size</code>可控制同时这个参数也控制online log每个读写块的大小)面即可。这个onlinelog也是用临时文件存创建在innodb_tmpdir最大大小为参数<code>innodb_online_alter_log_max_size</code>控制如果超过这个大小了DDL就会失败。临时文件的名字也类似上述的排序临时文件的名字。</p>
<p>在online DDL的最后阶段需要把排序完的文件和中途产生的DML全都应用到一个中间文件上中间文件文件名类似<code>#sql-ib53-522550444.ibd</code>,其中<code>#sql-ib</code>是固定的前缀,<code>53</code>是InnoDB层的table id<code>522550444</code>是随机生成的数字。同时在server层也会生成一个frm文件(8.0中没有),文件名类似<code>#sql-4d2b_2a.frm</code>,其中<code>#sql</code>是固定前缀,<code>4d2b</code>是进程号的十六进制表示,<code>2a</code>是线程号的十六进制表示(show processlist中的id)。因此我们也可以通过这个命名规则来找到哪个线程在做DDL。这里需要注意一点这里说的中间文件其实算是一个临时表并不是上文说中临时文件这些中间文件可以通过ls来查看。当在DDL中的最后一步会把这两个临时文件命名回原来的表名。正因为这个特性所以当数据库中途crash的时候可能会在磁盘上留下残余无用的文件。遇到这种情况可以先把frm文件重命名成与ibd文件一样的名字然后使用<code>DROP TABLE</code>#mysql50##sql-ib53-522550444`来清理残余的文件。注意如果不用drop命令直接删除ibd文件可能会导致数据字典里面依然有残余的信息做法不太优雅。当然在8.0中,由于使用了原子的数据字典,就不会出现这种残余文件了。</p>
<h4>BinLog中的缓存操作</h4>
<p>BinLog只有在事务提交的时候才会写入到文件中在没提交前会先放在内存中(由参数<code>binlog_cache_size</code>控制)如果内存放慢了就会创建临时文件使用方法也是先通过mkstemp创建然后直接unlink留一个句柄读写。临时文件名类似<code>MLXXXXXX</code>,其中<code>ML</code>是固定前缀,<code>XXXXXX</code>是大小写字母以及数字的随机组合。单个事务的BinLog太大可能会导致整个BinLog的大小也过大从而影响同步因此我们需要尽可能控制事务大小。</p>
<h4>优化创建的临时文件</h4>
<p>有些操作除了在引擎层需要依赖隐式临时表来辅助复杂SQL的计算在Server层也会创建临时文件来辅助比如order by操作会调用filesort函数。这个函数也会先使用内存(sort_buffer_size)排序,如果不够,就会创建一个临时文件,辅助排序。文件名类似<code>MYXXXXXX</code>,其中<code>MY</code>是固定前缀,<code>XXXXXX</code>是大小写字母以及数字的随机组合。</p>
<h4>Load data中用的临时文件</h4>
<p>在BinLog复制中如果在主库上使用了Load Data命令即从文件中导数据数据库会把整个文件写入到RelayLog中然后传到备库备库解析RelayLog从中抽取出对应的Load文件然后在备库上应用。备库上这个文件存储的位置由参数<code>slave_load_tmpdir</code>控制。文档中建议这个目录不要配置在物理机的内存目录或者重启后会删除的目录。因为复制依赖这个文件,如果意外被删除,会导致复制中断。</p>
<h4>其他</h4>
<p>除了上文所述的几个地方外,还有其他几个地方也会用到临时文件:</p>
<ul>
<li>在InnoDB层启动的时候会创建多个临时文件用来存储最后一次外键或者唯一键错误; 最后一次死锁的信息; 最后的innodb状态信息。用临时文件而不用内存的原因猜测是内存使用率不会因为写这些指标而波动。</li>
<li>在Server层分区表使用show create table时会用到临时文件。另外在MYISAM表内部排序的时候也会用到临时文件。</li>
</ul>
<h2>相关参数</h2>
<p>** 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机制。</p>
<h2>总结建议</h2>
<p>MySQL的临时表以及临时文件其实是一个比较复杂的话题涉及的模块比较多出现的时机比较难把握导致排查问题相比普通表也难不少。建议读者结合代码细细研究这样才能定位在线上可能出现的棘手问题。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/MySQL 主从复制 半同步复制.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997fe02bdb8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>