MySQL存储过程不预编译常驻内存,而是每次CALL按需分配独立执行上下文;内存消耗集中在结果集缓冲、局部变量、嵌套调用帧及动态SQL解析树,且随连接数线性增长。

mysql_real_query() 执行普通 SQL 时走文本协议,而存储过程本身在创建时就完成了语法解析、权限检查、执行计划生成这三步——这些中间产物会缓存在内存中,与连接无关;但调用时若涉及临时表、游标、变量堆栈或嵌套调用,MySQL 会为每个连接实例分配独立的执行上下文空间。
存储过程调用时的内存分配发生在连接生命周期内
MySQL 不会为存储过程本身单独“预编译并常驻内存”,而是按需为每次 CALL 分配运行时结构。常见内存消耗点包括:
-
SELECT ... INTO或游标遍历时,结果集缓冲区(tmp_table_size和max_heap_table_size限制)会被每个连接独占一份 - 局部变量(尤其是
TEXT/BLOB类型)在存储过程体中声明后,其内存由连接线程私有堆分配,不共享、不复用 - 嵌套调用深度 > 3 层时,MySQL 会为每层保存调用帧(call frame),含参数拷贝、返回地址、作用域符号表指针,这部分开销随并发连接数线性增长
- 使用
PREPARE+EXECUTE在存储过程中动态构造 SQL,会导致每个连接都缓存一份Statement对象及其解析树,无法跨连接复用
为什么 GUI 工具或连接池场景下更明显
很多客户端(如 Navicat、DBeaver)或连接池(如 HikariCP)默认开启 autocommit=1 且不显式 COMMIT,导致事务隐式开启;而存储过程内部若含 START TRANSACTION 或隐式锁操作,会使连接长期持有锁资源+事务状态+undo log 引用,间接推高内存占用。
更隐蔽的是:某些驱动在复用连接时未重置存储过程相关线程变量(如 @last_insert_id、用户变量 @var),MySQL 会为这些变量保留额外元数据结构,尤其在长连接+高频 CALL 场景下累积明显。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
排查时重点关注 performance_schema 中这几项
直接查当前活跃连接的内存消耗来源:
SELECT * FROM performance_schema.memory_summary_by_thread_by_event_name WHERE event_name LIKE 'memory/sql/%' AND SUM_NUMBER_OF_BYTES_ALLOC > 1048576;SELECT THREAD_ID, PROCESSLIST_INFO FROM performance_schema.threads WHERE TYPE = 'FOREGROUND' AND PROCESSLIST_INFO LIKE 'CALL%';- 对比
SHOW STATUS LIKE 'Threads_connected';与SHOW STATUS LIKE 'Handler_read%';,若连接数高但Handler_read_rnd_next极低,说明大量连接卡在存储过程变量初始化或游标打开阶段
真正容易被忽略的是:存储过程不是“越预编译越省内存”,而是“越复杂、越依赖运行时上下文,就越吃单连接内存”。别只盯着 query_cache_size 或 innodb_buffer_pool_size,先看 performance_schema 里每个线程到底在 malloc 什么。










