盘点 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-8import 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 来说,上述的语句执行后,会先创建一个内存内部临时表,存储 complementationcount(*) 的值,主键为 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 还会在下列情况下创建内部临时表:

  1. 对于UNION语句的评估,但有一些后续描述中的例外情况。
  2. 对于某些视图的评估,例如使用 TEMPTABLE 算法、UNION 或聚合的视图。
  3. 对派生表的评估。
  4. 对公共表达式的评估。
  5. 用于子查询或半连接材料化的表。
  6. 对包含 ORDER BY 子句和不同 GROUP BY 子句的语句的评估,或者对于其中 ORDER BY 或 GROUP BY 子句包含来自连接队列中第一个表以外的表的列的语句。
  7. 对于 DISTINCT 与 ORDER BY 的组合,可能需要一个临时表。
  8. 对于使用 SQL_SMALL_RESULT 修饰符的查询,MySQL 使用内存中的临时表,除非查询还包含需要在磁盘上存储的元素。
  9. 为了评估从同一表中选取并插入的 INSERT … SELECT 语句,MySQL 创建一个内部临时表来保存 SELECT 的行,然后将这些行插入目标表中。
  10. 对于多表 UPDATE 语句的评估。
  11. 对于 GROUP_CONCAT() 或 COUNT(DISTINCT) 表达式的评估。
  12. 窗口函数的评估,根据需要使用临时表。

值得注意的是,某些查询条件 MySQL 不允许使用内存内部临时表,在这种情况下,服务器会使用磁盘内部临时表。

  1. 表中存在 BLOB 或 TEXT 列。MySQL 8.0 中用于内存内部临时表的默认存储引擎 TempTable 从 8.0.13 开始支持二进制大对象类型。
  2. 如果使用了 UNION 或 UNION ALL,SELECT 的列表中存在任何最大长度超过 512 的字符串列(对于二进制字符串为字节,对于非二进制字符串为字符)。
  3. 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 获取

类型地址
版本库https://github.com/actiontech/sqle
文档https://actiontech.github.io/sqle-docs/
发布信息https://github.com/actiontech/sqle/releases
数据审核插件开发文档https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/133721.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宝马——使用人工智能制造和驾驶汽车

德国汽车制造商宝马(BMW)每年在全球制造和销售250万台汽车&#xff0c;其品牌包括宝马、MINI和劳斯莱斯。 宝马汽车以其卓越的性能和对新技术的应用而著名&#xff0c;它是道路上最精致的汽车之一&#xff0c;并且和其竞争对手戴姆勒(Daimler)一样&#xff0c;在将自动驾驶汽车…

Redis中的Zset类型

目录 Zset的相关命令 zadd zrange zcard zcount zrevrange zrangebyscore zpopmax bzpopmax zpopmin和bzpopmin zrank zrevrank zscore zrem zremrangebyrank zremrangebyscore 操作集合间的命令 zinterstore和zunionstore 内部编码 Zset的应用场景 Zset表…

独立键盘接口设计(Keil+Proteus)

前言 软件的操作参考这篇博客。 LED数码管的静态显示与动态显示&#xff08;KeilProteus&#xff09;-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/134101256?spm1001.2014.3001.5501实验&#xff1a;用4个独立按键控制8个LED指示灯。 按下k1键&#x…

数字化转型需要RPA,那么RPA如何落地?

首先&#xff0c;我们来探讨一下RPA为什么这么重要&#xff0c;并非一个简单的自动化脚本就可以代替的。 主要从高效性、准确性、稳定性三方面体现RPA流程自动化的价值。 一、高效性 相比人类员工&#xff0c;机器人可以以飞快的速度完成工作&#xff0c;而且不需要休息或中…

Mysql进阶-视图篇

介绍 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询中使用的表&#xff0c;并且是在使用视图时动态生成的。 通俗的讲&#xff0c;视图只保存了查询的SQL逻辑&#xff0c;不保存查询结果。…

Si4010 一款带有MCU SoC RF发射机芯片 无线遥控器

Si4010是一款完全集成的SoC RF发射机&#xff0c;带有嵌入式CIP-51 8051 MCU&#xff0c;专为1GHz以下ISM频带设计。该芯片针对电池供电的应用进行了优化&#xff0c;工作电压为1.8至3.6 V&#xff0c;待机电流小于10 nA的超低电流消耗。高功率放大器可提供高达10 dBm的输出功率…

手术训练系统项目

★ 手术训练系统项目 项目描述&#xff1a;手术训练系统&#xff0c;它提供了多项功能&#xff0c;包括账户登录与创建、数据库与账户管理、课程管理、小组管理、成绩统计、证书发布、训练和系统设置。 职责描述: 1、训练功能开发&#xff08;任务概述、任务指导、评分规则、评…

【数据结构】手撕单链表

目录 前言 1 链表 1.1 链表的概念及结构 1.2 链表的分类 1.2.1 单向或者双向 1.2.2 带头或者不带头 1.2.3 循环或者非循环 1.2.4 无头单向非循环链表 1.2.5 带头双向循环链表 2 链表的实现 2.1 结构 2.2 结点的创建 2.3 尾插 2.4 头插 2.5 尾删 2.6 头删 2.7 …

数据结构与算法之美学习笔记:17 | 跳表:为什么Redis一定要用跳表来实现有序集合?

目录 前言如何理解“跳表”&#xff1f;用跳表查询到底有多快&#xff1f;跳表是不是很浪费内存&#xff1f;高效的动态插入和删除跳表索引动态更新解答开篇内容小结 前言 本节课程思维导图&#xff1a; 二分查找底层依赖的是数组随机访问的特性&#xff0c;所以只能用数组来实…

2.求100-999之间的水仙花数

#include<stdio.h>void fun(void){int i,a,b,c;for(i100;i<1000;i) {ai%10;//个 b(i/10)%10;//十 ci/100;//百 if(ia*a*ab*b*bc*c*c)printf("%d ",i);}}int main(){fun();return 0;}

mysql的高阶语句

mysql的高阶语句 数据库的权限一般是很小的&#xff0c;我们在工作使用最多的场景就是查 排序 分组 子查询 视图 多表连接查询&#xff08;左连接 右连接 内连接&#xff09;别名 使用select语句&#xff0c;使用order by 查看&#xff1a;select id,name,score from info or…

五表联筛:从五个表格中筛选出出现过两次及两次以上的人名

五表联筛&#xff1a;从五个表格中筛选出出现过两次及两次以上的人名 需求分析&#xff1a; 1.把五个表格合并起来&#xff0c;合并之前必须确保五个表格的项是一样 2.合并之后查找哪些人出现过两次 3.最后输出结果代码&#xff1a; def delete_from(self):# 读取五份表格文件…

STM32 GPIO 描述

一、GPIO功能描述 每个GPIO端口有两个32位配置寄存器(GPIOx_CRL&#xff0c;GPIOx_CRH) &#xff0c;两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR) &#xff0c;一个32位置位/复位寄存器(GPIOx_BSRR)&#xff0c;一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR…

数据可视化:地图

1.基础地图的使用 如何添加颜色表示层级 代码实现 """基础地图的使用 """ from pyecharts.charts import Map from pyecharts.options import VisualMapOpts# 准备地图对象 map Map() # 准备数据 data [("北京市", 9),("上海市…

实验记录之——git push

平时做开发的时候经常push代码不成功&#xff0c;如下图 经好友传授经验&#xff0c;有如下方法 Win cmd使用Clash&#xff08;端口是7890&#xff09;代理操作&#xff0c;在cmd中输入&#xff1a; set http_proxy127.0.0.1:7890 set https_proxy127.0.0.1:7890Linux export …

有关LED显示屏对比度的知识

LED显示屏的对比度是指显示屏的亮度范围&#xff0c;即显示屏能够显示的最亮和最暗的部分之间的差异。对比度是一个重要的显示参数&#xff0c;它影响图像和视频的质量&#xff0c;以及用户对显示内容的感知。你知道LED显示屏的亮度和对比度是如何调节的吗&#xff1f; 一般来说…

【渗透测试】垂直越权(高危)、水平越权(中危)

目录 一、简介1.1 水平越权&#xff08;中危&#xff09;1.2 垂直越权&#xff08;高危&#xff09;1.3 方便记忆方法 二、修复方案2.1 水平越权修复2.2 垂直越权修复 一、简介 1.1 水平越权&#xff08;中危&#xff09; 漏洞危害&#xff1a; 水平越权 是相同级别&#xff0…

智能井盖传感器实时批发价格

城市之中高楼大厦林立&#xff0c;越来越多的人群涌入一线城市或二线城市。同时即便是县城之中接连不断的高楼大厦拔地而起&#xff0c;住宅小区的面积在不断拓宽。随着这一系列情况的出现&#xff0c;首先要完善的是每一个地区的城市道路设施建设。无论小区还是在城市路面之中…

antv G6 开发踩坑记录

1、通过键盘 鼠标拖拽创建边 根据官方实例&#xff0c;在画布上创建边基本都是点击端点构建边&#xff0c;或者固定端点后拖拽创建边&#xff0c;但是倘若有个这样的需求&#xff1a;“键盘按住ctrl后&#xff0c;鼠标从一个端点拖拽出边到另一个端点来创建边” 改如何应对呢…

使用c++解压rar文件,基于UnRAR64,非命令行

最近项目需要解压缩rar文件&#xff0c;我们都知道rar是闭源收费软件&#xff0c;如果直接采用命令行可能会有限制&#xff0c;或者盗版问题&#xff0c;使用正版的winrar命令行解压rar文件是否有限制&#xff0c;这个我没来得及测试&#xff0c;但是从交互体验上来说&#xff…