max_execution_time对存储过程完全无效,因其仅作用于独立只读SELECT语句,而存储过程内所有语句属子执行单元,绕过超时检查;替代方案包括EVENT轮询PROCESSLIST主动KILL QUERY,或在过程内手动埋点检测时间并LEAVE退出。

MySQL存储过程本身不支持max_execution_time,设了也无效——这是硬限制,不是配置漏了。
为什么CALL会超时,但max_execution_time完全不起作用?
因为该参数只对独立执行的SELECT语句生效。存储过程内部的任何SELECT、UPDATE或嵌套逻辑,哪怕加了/*+ MAX_EXECUTION_TIME(100) */提示,MySQL内核压根不检查。
- ✅ 生效场景:
SELECT * FROM users WHERE id = 123(直连后单条读) - ❌ 无效场景:
CALL proc_analyze_report()、INSERT ... SELECT里的子查询、触发器/函数内的SELECT - ⚠️ 注意:
SET SESSION max_execution_time = 500在存储过程里执行,仅影响后续新发的独立SELECT,不影响当前过程体
怎么定位到底是哪一段卡住了?
别只看报错时间,先查实时线程状态:
- 执行
SHOW FULL PROCESSLIST,筛选Command = 'Query'且Time > 30的行,确认Info字段是否为CALL xxx() - 若状态是
Locked或Waiting for table metadata lock,说明卡在锁上,不是慢查询 - 若状态是
Sending data或Sorting result,大概率是某条内部SQL没走索引,用EXPLAIN拆解过程里对应语句(可临时把关键SQL抽出来单独跑) - 检查过程内是否有
SELECT ... FOR UPDATE或未提交的事务分支——异常路径漏ROLLBACK会导致连接长期挂起
Java调用后连接池耗尽,90%是资源没关干净
CallableStatement不关、ResultSet不消费完,连接就永远“忙”着等下一个结果集,池里空闲数不会增加。
- 必须用
try-with-resources:try (CallableStatement cs = conn.prepareCall("{CALL proc_stat(?)}")) { cs.setInt(1, 1001); cs.execute(); // 必须循环获取所有结果集,哪怕只取第一个 try (ResultSet rs = cs.getResultSet()) { while (rs.next()) { /* consume */ } } // 若有多个结果集,需反复调用 getMoreResults() + getResultSet() } - 手动管理时,关闭顺序必须是:
ResultSet→CallableStatement→ (不关Connection) - HikariCP开启泄露检测:
leakDetectionThreshold=60000,超60秒没归还会打堆栈,直接定位到哪行cs.execute()后面忘了关
存储过程返回多个ResultSet,漏处理一个就卡死
JDBC要求客户端显式消费全部结果集。过程里写了3个SELECT,你只取了第一个,剩下两个一直等着被getMoreResults()拉走,连接就停在Sleep状态但实际不可复用。
- 调用
cs.execute()后,必须循环:do { try (ResultSet rs = cs.getResultSet()) { if (rs != null) { while (rs.next()) { /* process */ } } } } while (cs.getMoreResults()); - Spring JDBC的
SimpleJdbcCall默认只取第一个ResultSet,多结果场景必须换原生CallableStatement处理 - MySQL日志里看到大量
Sleep线程且Time持续增长,十有八九是这个原因
最常被忽略的点:超时表现出来的“连接池满”,根源往往不在SQL本身多慢,而在连接根本没被释放——要么过程里锁没释放,要么应用层没关结果集,要么事务没提交。先盯住PROCESSLIST里的状态和时间,再决定是优化SQL还是改代码。


















