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

简介: 很多DBA看到EXPLAIN输出里的Using temporary就紧张,觉得查询“肯定慢”。但Using temporary不等于“磁盘临时表”——MySQL优先在内存中创建临时表,只有数据量超过阈值才会落盘。本文拆解临时表的三种形态、触发条件、内存与磁盘的判定方法,以及4种优化方案,帮你精准判断什么时候该出手、什么时候可以不管。

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

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

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:

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秒。

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 不愁

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

相关文章
|
1月前
|
SQL 监控 关系型数据库
MySQL索引合并优化器陷阱:为什么复合索引比索引合并快一个数量级?
MySQL优化器有一个“自作聪明”的行为——当单列索引无法完全覆盖查询时,它可能选择索引合并(Index Merge) ,同时使用多个单列索引,把结果集合并起来。听起来很合理对吧?但索引合并有严格的适用条件,用错了比全表扫描还慢——尤其是UNION类型的索引合并,需要对多个结果集去重和排序,代价极高。本文拆解索引合并的3种类型、3个踩坑场景,以及什么时候该用复合索引替代。
|
1月前
|
SQL 关系型数据库 MySQL
别再盯着EXPLAIN的rows列了,8.0.18之后有更好的选择
EXPLAIN是DBA最常用的工具之一,但大多数人还在看type、rows、Extra这些传统字段——然后靠经验猜。MySQL 8.0.18开始引入的EXPLAIN ANALYZE,直接把实际执行时间和行数输出给你看,不用猜了。本文对比传统EXPLAIN和EXPLAIN ANALYZE的差异,展示如何用新工具把执行计划分析这件事从“猜”变成“看”。
|
1月前
|
存储 SQL 容灾
共享存储集群 vs 分布式多副本:同城双活两条技术路线怎么选?
同城双活正在成为金融、政务等核心系统的容灾标配——RPO=0、RTO<30秒。但真正的落地远不止“两个机房各放一套数据库”那么简单。网络延迟的容忍度、脑裂预防机制、同步复制的性能代价、以及故障切换后的数据回滚,每一个环节都可能成为“最后一公里”的绊脚石。本文从容灾架构演进入手,拆解同城双活的核心技术原理、关键挑战与应对方案,并结合同城双中心方案及实测数据进行深度解析。
|
1月前
|
SQL 存储 关系型数据库
分区裁剪失效、DDL卡死、元数据爆炸:分区表的3个真实代价
很多人觉得分区表是“轻量级分库分表”——数据分开放、查询只扫一个区、过期数据直接DROP分区,听起来很完美。但分区表有严格的适用边界和隐藏代价:分区键选错导致所有查询都扫全部分区、分区数量过多导致DDL巨慢、跨分区查询比普通表还慢……本文从分区表的核心原理出发,拆解4种分区类型、3个真实踩坑场景,以及分区表与分库分表的本质区别,帮你一次性搞清楚到底该不该用。
|
1月前
|
SQL 运维 算法
订单表上亿行,我按用户ID拆成128片之后怎么样了
从单表几千万行慢查询的痛点出发,讲清垂直拆分与水平拆分的区别、分片键怎么选、分片算法(hash取模/range/一致性hash)怎么权衡,以及分库分表带来的分布式ID、跨片查询、分布式事务等问题,给出避免过度拆分的避坑清单。
|
1月前
|
SQL 人工智能 数据库
AI写的SQL语法对、性能炸?上线前五道关卡能救命
从AI生成SQL的三大翻车模式(字段幻觉、性能灾难、语义错误)出发,给出上线前五道审核关卡:结构预检、执行计划校验、高危操作拦截、灰度上线、审计追踪,附SQL示例与避坑清单。
|
1月前
|
安全 关系型数据库 MySQL
高并发下1档只慢一点?innodb_flush_log_at_trx_commit的0/1/2实测
生成图片:不要沿用上面的图片风格,重新生成 4 张文章封面图供我选择,16:9 主标题:MySQL持久性最佳实践 副标题:redo刷盘参数三档取舍与故障分析 文章概述:实测innodb_flush_log_at_trx_commit的0、1、2三档性能,讲清进程崩溃与断电下的丢数据边界,以及redo、doublewrite、双1的关系,给出选型建议。
|
1月前
|
SQL 人工智能 运维
3个月AI Agent运维实测:慢SQL它管,根因还得我上
以三个月实测的视角,划清AI Agent自治运维的真实能力边界:巡检、慢SQL发现等重复活已可替代,复杂根因、变更审批、数据兜底仍需人把关,探讨DBA角色从救火队员向定规则、把关人的转型。
|
1月前
|
SQL 运维 关系型数据库
2026年分布式数据库有哪些主流选择?先评估这5个维度再决定
分布式数据库选型,99%的文章在列表格比参数——但真正的决策关键不在厂商PPT里,在上线后的真实运维里。本文从网络延迟容忍度、SQL兼容性验证、在线扩容能力、全局索引代价、运维工具链成熟度5个维度出发,给出可落地的评估方法和决策建议,帮助你在选型阶段避开那些“只有上线后才会发现”的坑。
|
1月前
|
JSON 关系型数据库 MySQL
MySQL 5.7升级到8.0之后,JSON查询的性能瓶颈真的解决了吗?
MySQL 5.7引入原生JSON类型,8.0支持多值索引,至今已近十年。但大量开发人员仍在把JSON当“万能兜底字段”——不管什么数据都往里塞,等查询慢到怀疑人生才想起来排查。本文拆解JSON字段查询的5个高频踩坑场景,给出虚拟列索引、多值索引等正确的优化方案。