mysql存储过程引发锁等待超时的主因是其内部事务未及时提交或回滚,导致行锁长期持有;常见诱因包括缺失索引引发全表扫描、嵌套调用延长事务、自增锁争用及错误处理缺失。

存储过程里藏着长事务,锁就卡死了
存储过程本身不直接导致锁等待超时,真正出问题的是它内部隐式或显式开启的事务没及时结束。MySQL 存储过程中一旦执行 BEGIN ... END 块里的 UPDATE、DELETE 或 SELECT FOR UPDATE,InnoDB 就会加行锁;如果后续还有复杂逻辑(比如循环、条件判断、临时表操作),而没在合适位置 COMMIT 或 ROLLBACK,这个锁就会一直挂着——别的连接一碰相同数据就等,等够 innodb_lock_wait_timeout 秒就报错。
常见现场特征:
-
INNODB_TRX里能看到trx_state = 'RUNNING'但trx_started时间远早于当前时间(比如差几分钟),且trx_query是存储过程名或空 -
PROCESSLIST中对应线程STATE是Updating或Waiting for table level lock,但INFO字段为空(说明 SQL 已执行完,控制权在存储过程逻辑里) - 调用同一个存储过程的并发请求越多,超时越频繁,且错误日志里
Lock wait timeout exceeded后紧跟的不是具体 SQL,而是CALL proc_name(...)
参数传进来没走索引,整张表都锁了
存储过程里常把参数拼进动态 SQL 或直接用于 WHERE 条件,但开发者容易忽略:如果参数列没索引,或者用了函数包装(如 WHERE DATE(create_time) = ?),EXPLAIN 一看就是 type: ALL ——全表扫描意味着对所有扫描到的行都加意向锁甚至行锁。高并发调用下,不同会话锁的行集重叠度高,互相阻塞概率陡增。
检查和修复建议:
- 对存储过程内所有被 WHERE / JOIN / ORDER BY 引用的参数列,确认是否建了索引:
SHOW INDEX FROM table_name WHERE Column_name = 'xxx'; - 避免在 WHERE 中对参数列使用函数、类型转换、
LIKE '%xxx'等导致索引失效的操作 - 若必须模糊匹配,考虑前缀索引或全文索引,而不是放弃索引硬扫
- 在存储过程开头加
EXPLAIN FORMAT=JSON对关键语句做预检(仅开发环境),例如:SET @sql = CONCAT('EXPLAIN FORMAT=JSON SELECT * FROM t WHERE id = ', p_id); PREPARE stmt FROM @sql; EXECUTE stmt;
嵌套调用 + 外部依赖,事务像橡皮筋越拉越长
存储过程 A 调用存储过程 B,B 再调用 C,中间还夹着 SLEEP()、GET_LOCK()、HTTP 请求(通过 UDF)、文件读写……这些操作不参与事务管理,但会让持有锁的时间不可控。更危险的是:MySQL 默认不支持存储过程内的事务自动传播,START TRANSACTION 在子过程中可能被忽略,导致外层事务意外延长。
关键规避点:
- 禁止在存储过程里调用任何阻塞型外部资源(RPC、curl、SLEEP);必须做的,移到应用层处理
- 明确每个存储过程的事务边界:要么全部由调用方控制(即存储过程内不写
START/COMMIT/ROLLBACK),要么内部用DECLARE EXIT HANDLER保证异常时回滚,且只在最后一步提交 - 检查
autocommit设置:SELECT @@autocommit;。若为 0,且存储过程没显式COMMIT,整个调用链都在一个未结束事务里 - 用
SELECT * FROM information_schema.INNODB_TRX WHERE trx_mysql_thread_id = ?查具体线程的锁持有行数(trx_rows_locked)和修改行数(trx_rows_modified),数值异常大基本可断定是全表扫描或事务失控
自增锁在批量 INSERT 场景下成瓶颈
很多存储过程封装了批量插入逻辑,比如循环 100 次执行 INSERT INTO t VALUES (...)。MySQL 5.7+ 默认 innodb_autoinc_lock_mode = 1(连续模式),整条语句执行完才释放自增锁;如果过程里是逐条 INSERT,每条都抢一次自增锁,高并发下排队明显。现象是:多个 CALL 几乎同时进入,但只有第一个能快速推进,其余全卡在 insert 状态,几秒后集体超时。
验证与调整:
- 查当前模式:
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';,值为 1 表示连续模式,2 表示交错模式(更轻量) - 交错模式(
innodb_autoinc_lock_mode = 2)要求 binlog_format = ROW,且需重启 MySQL 生效;线上切前务必验证主从一致性 - 更推荐重构:把循环 INSERT 改成单条
INSERT ... VALUES (...), (...), (...)批量插入,减少自增锁争用次数 - 若必须逐条插,考虑用
REPLACE INTO或INSERT IGNORE替代,它们在某些场景下锁行为更可控(但注意业务语义)
最易被忽略的一点:存储过程的错误处理缺失。没写 DECLARE CONTINUE HANDLER 或 EXIT HANDLER,导致某步出错后事务挂起、锁不释放,后续所有调用全被堵死——这种“静默卡死”比报错更难排查。











