触发器执行卡住导致锁超时,本质是其隐式延长事务持锁时间、扩大锁范围;需通过sys.innodb_lock_waits、performance_schema及EXPLAIN定位未走索引的DML等根本原因。

触发器执行卡住导致锁超时,怎么定位
触发器本身不直接报 Lock wait timeout exceeded,但它在事务中隐式执行,会延长持有锁的时间、扩大锁范围,最终让下游事务等不及。排查关键不是找“触发器语法错”,而是确认它是否成了阻塞链中那个迟迟不提交的环节。
常见错误现象包括:
- 某条
UPDATE或INSERT语句单独执行很快,但一进业务流程(尤其带事务边界)就频繁超时 -
INNODB_TRX中看到事务状态是RUNNING,trx_operation_state却卡在fetching rows或updating,而trx_query显示的是主表操作,不是触发器SQL - 用
sys.innodb_lock_waits查到阻塞源,blocking_query为NULL,但blocking_start_time很早——说明触发器所在事务早已执行完主语句,正“空转”挂着锁
实操建议:
- 先查触发器是否启用:
SELECT * FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_TABLE = 'your_table_name';,确认是否存在BEFORE UPDATE/AFTER INSERT等可能拖慢流程的类型 - 检查触发器内是否有隐式事务行为:比如调用了存储过程、执行了
INSERT INTO log_table、或包含未索引的WHERE查询——这些都会加新锁,且不随主语句结束而释放 - 在测试环境复现时,打开
performance_schema的事件采集:UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE 'events_statements%';,再查events_statements_history,能看见触发器内部 SQL 的实际执行耗时
触发器里的UPDATE没走索引,锁范围爆炸怎么办
这是最隐蔽也最致命的问题。主表语句走了索引,锁一行;触发器里一句 UPDATE stats SET cnt = cnt + 1 WHERE user_id = ? 却因 user_id 缺少索引,被迫扫描并锁全表——所有后续对 stats 表的操作全被堵死。
使用场景典型如:订单表 orders 的 AFTER INSERT 触发器去更新用户统计表 user_stats,但后者没建 user_id 索引。
实操建议:
- 对每个触发器中的 DML 语句单独执行
EXPLAIN,重点看type字段:必须是const、ref或range,绝不能是ALL或index - 用
SELECT * FROM information_schema.STATISTICS WHERE TABLE_NAME = 'user_stats' AND COLUMN_NAME = 'user_id';确认索引真实存在,且不是失效索引(INDEX_TYPE != 'BTREE'或SEQ_IN_INDEX != 1) - 如果触发器必须批量更新,改用
INSERT ... ON DUPLICATE KEY UPDATE替代UPDATE,避免全表扫描
MySQL 8.0 查不到触发器锁信息?别用 INNODB_LOCKS
MySQL 8.0 已彻底移除 INFORMATION_SCHEMA.INNODB_LOCKS 和 INNODB_LOCK_WAITS 这两张表,硬查会报 Unknown table。触发器引发的锁等待,必须转向 sys schema 和 performance_schema。
参数差异直接影响你能看到什么:
-
sys.innodb_lock_waits是视图,底层依赖performance_schema.data_locks和data_lock_waits,需确保performance_schema开启且相关 consumer 已启用 -
data_locks.LOCK_MODE字段值如X,REC_NOT_GAP表示行锁,X,GAP表示间隙锁——触发器若在 RR 隔离级别下执行范围查询,很可能引入间隙锁,扩大阻塞面 -
data_locks.LOCK_TRX_ID可关联到INNODB_TRX.trx_id,进而定位触发器所属事务的trx_started时间,判断是否长事务
实操建议:
- 立刻执行:
SELECT waiting_pid, blocking_pid, waiting_query, blocking_query, wait_age_secs FROM sys.innodb_lock_waits\G,注意看blocking_query是否为空,空则重点查blocking_pid对应的事务开始时间 - 查触发器事务的完整上下文:
SELECT THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO FROM performance_schema.threads t JOIN performance_schema.events_statements_current e USING (THREAD_ID) WHERE t.PROCESSLIST_ID = ?;,把?换成上一步拿到的blocking_pid
为什么KILL线程后触发器还继续阻塞
因为触发器不是独立线程,它依附于主语句所在的事务。KILL 一个线程,只终止其当前语句;如果事务没提交,锁依然由该事务持有——哪怕触发器代码已执行完,只要事务还在,锁就不放。
容易踩的坑:
- 误用
KILL QUERY:它只会中断触发器内的某条语句,但事务仍活跃,锁照常持有 - 忽略应用层事务管理:Java 的
@Transactional若配置了REQUIRES_NEW,触发器可能跑在嵌套事务里,KILL外层线程对内层无效 - 触发器调用了外部服务(如 HTTP 请求),而该请求超时未返回,事务卡在“等待响应”阶段,此时
trx_state是RUNNING,但trx_operation_state是sending data或空白
实操建议:
- 优先用
KILL(不带QUERY)终止线程,它会触发事务回滚,释放全部锁 - 查
INNODB_TRX时,重点关注trx_autocommit = 0且trx_state = 'ACTIVE'的记录,它们大概率是触发器所在事务 - 线上禁止触发器中做任何 I/O 操作,包括日志写入、远程调用、文件读写——这些都该移到应用层异步处理
复杂点在于:触发器逻辑藏在数据库里,不像应用代码那样容易打日志或设断点。一旦它参与了锁竞争,你看到的永远是“主语句在等锁”,而不是“触发器在锁表”。真正要命的,往往是那条你以为“只是更新个计数”的 UPDATE,因为没索引,悄悄锁住了整张统计表。











