临时表从4.7秒到0.12秒:不是所有Using temporary都需要优化

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

技术群里经常看到这样的对话:

A:"我这条SQL执行计划里有Using temporary,是不是要优化一下?"

B:"有临时表肯定慢啊,赶紧加索引!"

C:"不一定吧,我见过很多带Using temporary的查询跑得也挺快的。"

三个人说的都有道理,但都不完整。

Using temporary不等于"慢" 。MySQL创建临时表分三种情况:内存临时表、磁盘临时表、优化器临时表。只有磁盘临时表才是真正的性能杀手,内存临时表的开销其实没那么大。

今天把临时表这件事彻底拆开讲清楚。

一、临时表的三种形态

1. 内存临时表(Memory Temporary Table)

MySQL优先在内存中创建临时表,使用MEMORY存储引擎。数据全部在内存中操作,没有磁盘I/O。

触发条件:临时表数据量小于tmp_table_size和max_heap_table_size的限制。

性能影响:较小。内存操作比磁盘快几个数量级。

2. 磁盘临时表(On-Disk Temporary Table)

当内存临时表的数据量超过了tmp_table_size或max_heap_table_size的限制,MySQL会自动将临时表从内存转到磁盘。磁盘临时表使用InnoDB引擎(MySQL 8.0默认)或MyISAM引擎。

触发条件:临时表数据量超过阈值;或者查询中包含了TEXT/BLOB等无法在内存中处理的字段类型。

性能影响:巨大 。磁盘I/O比内存操作慢几十到几百倍。Created_tmp_disk_tables状态变量是核心监控指标。

3. 优化器临时表(派生表/物化临时表)

优化器为了优化查询而主动创建的临时表,比如将子查询物化为临时表、将DISTINCT的结果暂存等。性能影响取决于具体场景。

二、什么时候会触发临时表?

以下场景MySQL可能创建临时表:

场景 说明 是否必然慢
GROUP BY无索引 分组字段没有可用索引 大概率慢
DISTINCT无索引 去重字段没有合适索引 大概率慢
ORDER BY与GROUP BY字段不一致 分组和排序字段不同 大概率慢
UNION(不含ALL) 合并结果集需要去重 视数据量而定
派生表/子查询 优化器将子查询结果物化 视数据量而定
多表JOIN+ORDER BY 排序字段不在驱动表索引中 视数据量而定

GROUP BY的列没有可用索引时,MySQL必须先排序才能分组,而排序需要空间,于是建临时表。

三、怎么判断临时表是内存还是磁盘?

方法一:看状态变量

执行查询前后对比Created_tmp_tables和Created_tmp_disk_tables:

bash 复制代码
SHOW STATUS LIKE 'Created_tmp%';
-- 执行查询
SHOW STATUS LIKE 'Created_tmp%';

如果Created_tmp_disk_tables增加了,说明临时表落盘了------这是真正的性能警报 。如果Created_tmp_disk_tables / Created_tmp_tables > 5%,说明多数临时表已落盘,内存不够用。

方法二:看EXPLAIN FORMAT=TREE(MySQL 8.0+)

EXPLAIN FORMAT=TREE会显示更详细的执行信息,包括temporary table相关的实际行数。

四、4种优化方案

方案一:加合适的索引

大部分Using temporary的问题,一根联合索引就能解决。GROUP BY列必须是某个索引的最左前缀 。如果WHERE中有范围查询(>、<、BETWEEN),范围条件涉及的列必须放在索引后面,GROUP BY列必须前置。

方案二:调大临时表内存参数

修改tmp_table_size和max_heap_table_size,让更多临时表留在内存中。两个参数必须同步调整,临时表能否留在内存取决于两者中的较小值。生产环境建议设为64MB到256MB,避免默认16MB过小。

不能盲目调大------调得太大可能导致内存不足,影响整个数据库稳定性。

方案三:减少查询字段

SELECT *会让临时表单行数据变大,更容易触发落盘。只查询必要的字段,减小临时表单行数据大小。

方案四:改写SQL

把大范围聚合拆到离线任务或汇总表。比如日报表可以用定时任务提前计算好,而不是每次实时聚合。

五、一个真实案例

某报表系统,一条GROUP BY查询跑了4.7秒。

bash 复制代码
SELECT user_id, SUM(amount) as total_amount, COUNT(*) as tx_count
FROM payment_record
WHERE created_at >= '2024-06-01' AND created_at < '2024-07-01'
GROUP BY user_id
ORDER BY total_amount DESC LIMIT 20;

EXPLAIN显示Using temporary; Using filesort,临时表落盘了------临时表数据量超过tmp_table_size(默认16MB),写到了磁盘上。500万行数据,一个月范围80万行,GROUP BY跑了4.7秒。

优化方案:在(user_id, created_at)上建了一个覆盖索引 。GROUP BY的字段变成了索引的最左前缀,优化器可以直接利用索引有序性完成分组,不再需要临时表。

优化后:0.12秒 。从4.7秒到0.12秒,提升了近40倍。

六、小结

Using temporary不等于"慢"------内存临时表的开销其实没那么大。真正需要警惕的是磁盘临时表 。看到Using temporary先别急着加索引,先确认临时表是在内存还是磁盘、数据量有多大、查询本身是否在可接受范围内。大部分filesort + temporary的问题,一根设计合理的联合索引就能解决。优化之前,先搞清楚问题到底出在哪。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
l1t31 分钟前
测试DuckDB v2.1.0-alpha49002的bignum乘法
数据库·sql·算法·duckdb
麦壳饼1 小时前
SonnetDB:向量召回率评估中的精确基线与参数记录
数据库·sonnetdb
ClouGence1 小时前
数据如何随时可用?广西水牛先生的 MySQL 实时同步实践
数据库·mysql·数据分析
斯晨的AI笔记1 小时前
rsync 部署避坑:脚本执行位被 git 模式位抹掉,巡检备份双静默失败的排查与根治
数据库
这个DBA有点耶1 小时前
innodb_autoinc_lock_mode的三种模式:批量插入时自增锁的持有逻辑与调优
数据库·架构·代码规范
我命由我123452 小时前
5-1.Python 编码进阶 - 函数进阶(参数、返回值、递归、函数作为参数传递、lambda 匿名函数)
java·服务器·数据库·后端·python·java-ee·python3.11
小蒜学长2 小时前
基于Spring Boot+Vue3的在线游戏技术指导师匹配系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·在线游戏技术指导
Web3&Basketball3 小时前
DeepSeek V4.1 Flash 实测:Token 砍半、Search API 跑通
数据库·人工智能·python·大模型·api调用·deepseek·推理优化
川石课堂软件测试3 小时前
涨薪技术|0到1学会性能测试第9课-HTTP响应统计
数据库·功能测试·网络协议·http·kubernetes·单元测试·测试用例
竣达技术3 小时前
Modbus 转 SNMP 协议转换器|自定义协议 + SNMP TRAP 主动上报
运维·服务器·网络·数据库·安全