如何通过内存监控表分析MySQL SQL语句的实际内存消耗?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1029个文字,预计阅读时间需要5分钟。
markdownSHOW PROFILE 输出中的 Memory 列常被误读为SQL 占用内存,但实际上它是 MySQL 5.7 以前旧 profiler 的粗略估算值。从 MySQL 8.0 开始已被标记为 deprecated,默认禁用。即使开启,也只记录极简的分配事件(如 sort buffer 分配),不包括 join buffer、tmp table 内存、InnoDB 行锁结构开启关闭等。它无法反映执行器在真实压力下的内存行行为。
- 执行
SET profiling = 1后,SHOW PROFILES中的Memory值多数为空或为 0 - 该机制不跟踪内存释放,也不区分堆/栈/临时文件回退路径
- 并发场景下线程间内存统计互相干扰,数据不可靠
真正可用的是 performance_schema.memory_summary_by_thread_by_event_name
MySQL 5.7+ 默认启用 performance_schema,其中 memory_summary_by_thread_by_event_name 是唯一能按线程粒度追踪 SQL 执行期真实内存分配的表。关键在于:必须提前开启对应 instrument,否则全为空。
- 确认是否启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE 'memory/%' AND ENABLED = 'YES'; - 若结果极少,需手动开启:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'memory/sql/%' OR NAME LIKE 'memory/innodb/%' OR NAME LIKE 'memory/myisam/%'; - 每次连接后,先查
SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = CONNECTION_ID();,拿到当前线程 ID - 执行目标 SQL 前后,对比该线程的
SUM_NUMBER_OF_BYTES_ALLOC变化量,才是本次语句实际申请的堆内存总量
为什么不能只看 global 级别的 memory_summary_global_by_event_name
memory_summary_global_by_event_name 汇总了所有线程的内存事件,但无法绑定到某条 SQL。尤其在生产库中,多个查询并发执行时,你看到的“增长”可能是其他会话触发的缓冲区预分配、DDL 元数据缓存、甚至后台 purge 线程行为。它适合做趋势分析,不适合归因单条语句。
- 例如执行一条
SELECT ... JOIN后,memory/innodb/buf_buf_pool计数上涨——这大概率是 buffer pool 页面加载,而非该 SQL 独占内存 -
memory/sql/Query_cache在 MySQL 8.0+ 已移除,若还看到该 event,说明你连的是降级兼容模式或低版本实例 - 真正属于执行器的内存事件名有明确前缀:
memory/sql/JOIN_CACHE、memory/sql/Filesort_buffer、memory/sql/TABLE(临时表结构)
临时表和磁盘回退会让内存监控失效
当 SQL 触发磁盘临时表(Created_tmp_disk_tables 上升)或 filesort 落盘(Sort_merge_passes > 0),部分内存已转为 I/O 行为,performance_schema 中的内存统计不会体现这部分“本该用内存但没用上”的代价。此时看内存数字反而会低估真实资源消耗。
- 检查是否落盘:
SHOW STATUS LIKE 'Created_tmp%'; SHOW STATUS LIKE 'Sort%'; - 强制观察执行器行为:在 SQL 前加
EXPLAIN FORMAT=tree,留意是否有Using temporary; Using filesort提示 - 调大
tmp_table_size和max_heap_table_size后重测,对比memory/sql/TABLE增长量是否同步上升——若不升,说明原语句根本没进内存临时表路径
真实内存消耗不是某个数字,而是「执行器路径选择 + 配置边界 + 运行时竞争」共同作用的结果。最易被忽略的一点:同一语句在不同连接中可能走完全不同的内存路径——因为 performance_schema 的线程级统计依赖于线程初始化时的 instrument 开关状态,而这个开关在连接建立时就已冻结。换言之,你必须在连接后、执行前,确保 instrument 已开,且未被其他中间件或连接池复位。
本文共计1029个文字,预计阅读时间需要5分钟。
markdownSHOW PROFILE 输出中的 Memory 列常被误读为SQL 占用内存,但实际上它是 MySQL 5.7 以前旧 profiler 的粗略估算值。从 MySQL 8.0 开始已被标记为 deprecated,默认禁用。即使开启,也只记录极简的分配事件(如 sort buffer 分配),不包括 join buffer、tmp table 内存、InnoDB 行锁结构开启关闭等。它无法反映执行器在真实压力下的内存行行为。
- 执行
SET profiling = 1后,SHOW PROFILES中的Memory值多数为空或为 0 - 该机制不跟踪内存释放,也不区分堆/栈/临时文件回退路径
- 并发场景下线程间内存统计互相干扰,数据不可靠
真正可用的是 performance_schema.memory_summary_by_thread_by_event_name
MySQL 5.7+ 默认启用 performance_schema,其中 memory_summary_by_thread_by_event_name 是唯一能按线程粒度追踪 SQL 执行期真实内存分配的表。关键在于:必须提前开启对应 instrument,否则全为空。
- 确认是否启用:
SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE 'memory/%' AND ENABLED = 'YES'; - 若结果极少,需手动开启:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'memory/sql/%' OR NAME LIKE 'memory/innodb/%' OR NAME LIKE 'memory/myisam/%'; - 每次连接后,先查
SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = CONNECTION_ID();,拿到当前线程 ID - 执行目标 SQL 前后,对比该线程的
SUM_NUMBER_OF_BYTES_ALLOC变化量,才是本次语句实际申请的堆内存总量
为什么不能只看 global 级别的 memory_summary_global_by_event_name
memory_summary_global_by_event_name 汇总了所有线程的内存事件,但无法绑定到某条 SQL。尤其在生产库中,多个查询并发执行时,你看到的“增长”可能是其他会话触发的缓冲区预分配、DDL 元数据缓存、甚至后台 purge 线程行为。它适合做趋势分析,不适合归因单条语句。
- 例如执行一条
SELECT ... JOIN后,memory/innodb/buf_buf_pool计数上涨——这大概率是 buffer pool 页面加载,而非该 SQL 独占内存 -
memory/sql/Query_cache在 MySQL 8.0+ 已移除,若还看到该 event,说明你连的是降级兼容模式或低版本实例 - 真正属于执行器的内存事件名有明确前缀:
memory/sql/JOIN_CACHE、memory/sql/Filesort_buffer、memory/sql/TABLE(临时表结构)
临时表和磁盘回退会让内存监控失效
当 SQL 触发磁盘临时表(Created_tmp_disk_tables 上升)或 filesort 落盘(Sort_merge_passes > 0),部分内存已转为 I/O 行为,performance_schema 中的内存统计不会体现这部分“本该用内存但没用上”的代价。此时看内存数字反而会低估真实资源消耗。
- 检查是否落盘:
SHOW STATUS LIKE 'Created_tmp%'; SHOW STATUS LIKE 'Sort%'; - 强制观察执行器行为:在 SQL 前加
EXPLAIN FORMAT=tree,留意是否有Using temporary; Using filesort提示 - 调大
tmp_table_size和max_heap_table_size后重测,对比memory/sql/TABLE增长量是否同步上升——若不升,说明原语句根本没进内存临时表路径
真实内存消耗不是某个数字,而是「执行器路径选择 + 配置边界 + 运行时竞争」共同作用的结果。最易被忽略的一点:同一语句在不同连接中可能走完全不同的内存路径——因为 performance_schema 的线程级统计依赖于线程初始化时的 instrument 开关状态,而这个开关在连接建立时就已冻结。换言之,你必须在连接后、执行前,确保 instrument 已开,且未被其他中间件或连接池复位。

