盘点 MySQL 创建内部临时表的所有场景
作者总结了 MySQL 中所有触发使用内部临时表的场景。
作者:刘嘉浩,爱可生团队 DBA 成员,重度竞技游戏爱好者。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 2000 字,预计阅读需要 5 分钟。
临时表属于是一种临时存放数据的表,这类表在会话结束时会被自动清理掉,但在 MySQL 中存在两种临时表,一种是外部临时表,另外一种是内部临时表。
外部临时表指的是用户使用 CREATE TEMPORARY TABLE
手动创建的临时表。而内部临时表用户是无法控制的,并不能像外部临时表一样使用 CREATE 语句创建,MySQL 的优化器会自动选择是否使用内部临时表。
那么由此引发一个问题,MySQL 到底在什么时候会使用内部临时表呢?
我们将针对 UNION、GROUP BY 等场景进行分析。
UNION 场景
首先准备一个测试表。
CREATE TABLE `employees` ( `id` int NOT NULL AUTO_INCREMENT, `first_name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL, `last_name` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL, `sex` enum('M','F') COLLATE utf8mb4_bin DEFAULT NULL, `age` int DEFAULT NULL, `birth_date` date DEFAULT NULL, `hire_date` date DEFAULT NULL, PRIMARY KEY (`id`), KEY `last_name` (`last_name`), KEY `hire_date` (`hire_date`) ) ENGINE=InnoDB AUTO_INCREMENT=500002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
准备插入数据的脚本。
#! /usr/bin/python #! coding=utf-8 import random import pymysql from faker import Faker from datetime import datetime, timedelta # 创建Faker实例 fake = Faker() # MySQL连接参数 db_params = { 'host': 'localhost', 'user': 'root', 'password': 'root', 'db': 'db1', 'port': 3311 } # 连接数据库 connection = pymysql.connect(**db_params) # 创建一个新的Cursor实例 cursor = connection.cursor() # 生成并插入数据 for i in range(5000): id = (i+1) first_name = fake.first_name() last_name = fake.last_name() sex = random.choice(['M', 'F']) age = random.randint(20, 60) birth_date = fake.date_between(start_date='-60y', end_date='-20y') hire_date = fake.date_between(start_date='-30y', end_date='today') query = f"""INSERT INTO employees (id, first_name, last_name, sex, age, birth_date, hire_date) VALUES ('{id}', '{first_name}', '{last_name}', '{sex}', {age}, '{birth_date}', '{hire_date}');""" cursor.execute(query) # 每1000提交一次事务 if (i+1) % 1000 == 0: connection.commit() # 最后提交事务 connection.commit() # 关闭连接 cursor.close() connection.close()
在创建好测试数据后,执行一个带有 UNION 的语句。
root@localhost:mysqld.sock[db1]> explain (select 5000 as res from dual) union (select id from employees order by id desc limit 2); +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ | 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | | 2 | UNION | employees | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Backward index scan; Using index | | NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ 3 rows in set, 1 warning (0.00 sec)
可见第二行中 key 值是 PRIMARY,即第二个查询使用了主键 ID。第三行 extra 值是 Using temporary,表明在对上面两个查询的结果集做 UNION 的时候,使用了临时表。
UNION 操作是将两个结果集取并集,不包含重复项。要做到这一点,只需要先创建一个只有主键的内存内部临时表,并将第一个子查询的值插入进这个表中,这样就可以避免了重复的问题。因为值 5000 早已存在临时表中,而第二个子查询的值 5000 就会因为冲突无法插入,只能插入下一个值 4999。
UNION ALL 与 UNION 不同,并不会使用内存临时表,下列例子是使用 UNION ALL 的执行计划。
root@localhost:mysqld.sock[db1]> explain (select 5000 as res from dual) union all (select id from employees order by id desc limit 2); +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ | 1 | PRIMARY | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | | 2 | UNION | employees | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Backward index scan; Using index | +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------+ 2 rows in set, 1 warning (0.01 sec)
因为 UNION ALL 并不需要去重,所以优化器不需要新建一个临时表做去重的动作,执行的时候只需要按顺序执行两个子查询并将子查询放在一个结果集里就好了。
可以看到,在实现 UNION 的语义上,临时表起到的是一个暂时存储数据并做去重的动作的这么一种作用的存在。
GROUP BY
除了 UNION 之外,还有一个比较常用的子句 GROUP BY 也会使用到内部临时表。下列例子展示了一个使用 ID 列求余并进行分组统计,且按照余数大小排列。
root@localhost:mysqld.sock[db1]> explain select id%5 as complementation,count(*) from employees group by complementation order by 1; +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+ | 1 | SIMPLE | employees | NULL | index | PRIMARY,last_name,hire_date | hire_date | 4 | NULL | 5000 | 100.00 | Using index; Using temporary; Using filesort | +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+----------------------------------------------+ 1 row in set, 1 warning (0.00 sec)
可以看到 extra 的值是 using index、using temporary、using filesort; 这三个值分别是:使用索引、使用临时表、使用了排序。
注意:在 MySQL 5.7 版本中 GROUP BY 会默认按照分组字段进行排序,在 MySQL 8.0 版本中取消了默认排序功能,所以此处使用了 ORDER BY 进行复现。
对于 GROUP BY 来说,上述的语句执行后,会先创建一个内存内部临时表,存储 complementation
与 count(*)
的值,主键为 complementation
。然后按照索引 hire_date
对应的 ID 值依次计算 id%5 的值记为 x
,如果临时表中没有主键为 x
的值,那么将会在临时表中插入记录;如果存在则累加这一行的计数 count(*)
。在遍历完成上述的操作后,再按照 ORDER BY 的规则对 complementation
进行排序。
在使用 GROUP BY 进行分组或使用 DISTINCT 进行去重时,MySQL 都给我们提供了使用 hint 去避免使用内存内部临时表的方法。
hint | 解释 |
---|---|
SQL_BIG_RESULT | 显式指定该 SQL 语句使用磁盘内部临时表,适合大数据量的操作;适用于 InnoDB 引擎与 Memory 引擎。 |
SQL_SMALL_RESULT | 显式指定该 SQL 语句使用内存内部临时表,速度更快,适合小数据量的操作;适用于 Memory 引擎。 |
下列是一个使用了 SQL_BIG_RESULT 的例子。
root@localhost:mysqld.sock[db1]> explain select SQL_BIG_RESULT id%5 as complementation,count(*) from employees group by complementation order by 1; +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+ | 1 | SIMPLE | employees | NULL | index | PRIMARY,last_name,hire_date | hire_date | 4 | NULL | 5000 | 100.00 | Using index; Using filesort | +----+-------------+-----------+------------+-------+-----------------------------+-----------+---------+------+------+----------+-----------------------------+ 1 row in set, 1 warning (0.00 sec)
从执行计划中我们可以看出,使用了 SQL_BIG_RESULT 这个 hint 进行查询后,在 extra 列中 Using Temporary 字样已经不见了,即避免了使用内存内部临时表。
其他场景
当然,除了上述两个例子外,MySQL 还会在下列情况下创建内部临时表:
- 对于UNION语句的评估,但有一些后续描述中的例外情况。
- 对于某些视图的评估,例如使用 TEMPTABLE 算法、UNION 或聚合的视图。
- 对派生表的评估。
- 对公共表达式的评估。
- 用于子查询或半连接材料化的表。
- 对包含 ORDER BY 子句和不同 GROUP BY 子句的语句的评估,或者对于其中 ORDER BY 或 GROUP BY 子句包含来自连接队列中第一个表以外的表的列的语句。
- 对于 DISTINCT 与 ORDER BY 的组合,可能需要一个临时表。
- 对于使用 SQL_SMALL_RESULT 修饰符的查询,MySQL 使用内存中的临时表,除非查询还包含需要在磁盘上存储的元素。
- 为了评估从同一表中选取并插入的 INSERT … SELECT 语句,MySQL 创建一个内部临时表来保存 SELECT 的行,然后将这些行插入目标表中。
- 对于多表 UPDATE 语句的评估。
- 对于 GROUP_CONCAT() 或 COUNT(DISTINCT) 表达式的评估。
- 窗口函数的评估,根据需要使用临时表。
值得注意的是,某些查询条件 MySQL 不允许使用内存内部临时表,在这种情况下,服务器会使用磁盘内部临时表。
- 表中存在 BLOB 或 TEXT 列。MySQL 8.0 中用于内存内部临时表的默认存储引擎 TempTable 从 8.0.13 开始支持二进制大对象类型。
- 如果使用了 UNION 或 UNION ALL,SELECT 的列表中存在任何最大长度超过 512 的字符串列(对于二进制字符串为字节,对于非二进制字符串为字符)。
- SHOW COLUMNS 和 DESCRIBE 语句使用 BLOB 作为某些列的类型,因此用于此结果的临时表是将会是磁盘内部临时表。
参考资料
[1]: 丁奇 《MySQL45讲》 37.什么时候会使用内部临时表?
[2]: 8.4.4 Internal Temporary Table Use in MySQL URL:https://dev.mysql.com/doc/refman/8.0/en/internal-temporary-tables.html
更多技术文章,请访问:https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。
SQLE 获取

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
WSGI与ASGI:两种Python Web服务器网关接口的比较
在当今的Web开发领域,选择合适的服务器网关接口(Server Gateway Interface,简称SGI)对于提高Web应用程序的性能和并发性至关重要。在Python中,有两种常见的SGI:WSGI和ASGI。本文将深入探讨这两种SGI的异同点。 1、WSGI的概念 首先,让我们了解一下WSGI(Web Server Gateway Interface)。 WSGI 是一种同步的网关接口,它使用线程来处理每个请求,通过简单的函数调用——一个可调用的对象(通常是一个函数)来处理HTTP请求,并返回HTTP响应。这意味着在一个请求被处理期间,其他请求必须等待。这种同步处理方式在处理大量并发请求时可能会导致性能问题,因此WSGI适用于处理低并发、IO密集型的应用程序,在这种环境下,同步的请求处理足够高效。由于这种限制,WSGI的典型实现包括Gunicorn、uWSGI和mod_wsgi等。 2、ASGI的概念 随着异步编程概念的普及,出现了另一种网关接口——ASGI(Asynchronous Server Gateway Interface)。 ASGI 是一种异步的网关接口,允许同时...
- 下一篇
理论+实践详解最热的LLM应用框架LangChain
本文分享自华为云社区《LangChain是什么?LangChain的详细介绍和使用场景》,作者:码上开花_Lancer 。 一、概念介绍 1.1 Langchain 是什么? 官方定义是:LangChain是一个强大的框架,旨在帮助开发人员使用语言模型构建端到端的应用程序,它提供了一套工具、组件和接口,可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain是一个语言模型集成框架,其使用案例与语言模型的使用案例大致重叠,包括文档分析和摘要、聊天机器人和代码分析。 简单来说,LangChain提供了灵活的抽象和AI优先的工具,可帮助开发人员将LLM应用程序从原型转化为生产环境。 它还提供了一套工具,可帮助开发人员构建上下文感知、推理应用程序, LangChain的工具包括聊天机器人、文档分析、摘要、代码分析、工作流自动化、自定义搜索等。 1.2 如何使用 LangChain? 要使用 LangChain,开发人员首先要导入必要的组件和工具,例如 LLMs, chat models, agents, chains, 内存功能。这些组件组合起来创建一个可以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Mario游戏-低调大师作品
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启