alter trigger会隐式持有元数据锁(mdl)直至语句结束,阻塞后续dml/ddl;innodb下为轻量级mdl锁,myisam下则升级为重量级表锁,整表不可读写。

ALTER TRIGGER 会全程持有元数据锁(MDL)
不是触发器本身锁表,而是修改它的 ALTER TRIGGER 语句会隐式申请并持有元数据锁(MDL),直到语句执行完毕或事务提交。只要这个锁没释放,后续所有对该表的 DML(INSERT/UPDATE/DELETE)和 DDL(ALTER TABLE 等)都会排队等待。
常见卡顿现象:SHOW PROCESSLIST 中看到状态为 Waiting for table metadata lock;information_schema.INNODB_TRX 查不到活跃事务,但写操作持续超时。
- 若此时表上有长事务(比如未提交的
SELECT ... FOR UPDATE或大范围UPDATE),ALTER TRIGGER就会被阻塞,锁等待链直接形成 - InnoDB 下 MDL 是轻量级的,只阻塞同表 DDL;MyISAM 下则会升级为重量级表写锁,整表不可读不可写
- 务必先确认引擎类型:
SHOW CREATE TABLE orders,混用引擎时风险更高
触发器内 SQL 未走索引导致锁升级
触发器不独立加锁,但它继承父语句的锁行为。如果触发器里写了 UPDATE stats SET count = count + 1 WHERE type = 'order',而 type 字段没索引,InnoDB 就会全表扫描并加 Next-Key Lock——效果接近锁表。
这种“伪表锁”在 REPEATABLE READ 隔离级别下尤其危险:范围条件(如 WHERE created_at > '2026-04-01')会触发间隙锁,连插入新行都可能被阻塞。
- 必须对触发器内每条 DML 做
EXPLAIN:重点看type是否为ALL或index,key是否非NULL - 别信“只是改一行”,
WHERE条件没索引,就是全表锁的起点 -
SELECT ... FOR UPDATE在触发器里出现,等同于主动申请新锁,极易和主事务形成交叉等待
AFTER 触发器放大锁持有时间与死锁风险
AFTER INSERT 或 AFTER UPDATE 在主行已加 X 锁后才执行,此时再对其他表做 DML(比如更新用户积分、写日志),等于在已有锁基础上叠加新锁请求,锁等待链拉得更长。
典型死锁链路:事务 A 更新 orders → 触发器更新 user_points;事务 B 先更新 user_points → 再更新 orders。InnoDB STATUS 里能看到跨表反向等待,且语句含 NEW.id。
- 严禁在触发器里
UPDATE同一张表,哪怕WHERE id = NEW.id—— InnoDB 已持锁,再申请就是循环等待 - 跨业务表操作(如订单触发器改余额)必须移出触发器,统一由应用层按固定顺序加锁
- 多级嵌套触发器(AFTER → BEFORE → AGAIN)会让锁路径失控,8.0+ 才能在
INNODB STATUS里准确定位内层语句
MyISAM 表上改触发器 = 实质性表锁
MyISAM 不支持行锁,所有写操作默认加表级写锁。ALTER TRIGGER 虽不操作数据,但需确保触发器定义与表结构一致,因此必须申请表写锁。
只要有一个慢查询正持有该表的读锁,ALTER TRIGGER 就得等;一旦它拿到写锁,其他所有读写请求全部阻塞——这不是“看起来像锁表”,就是真锁表。
- 线上环境务必避免 MyISAM 表配触发器,尤其高并发场景
- 迁移前用
SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'db' AND TABLE_NAME = 't'批量检查引擎 - 即使表是 InnoDB,也要警惕触发器里调用的存储过程用了
LOCK TABLES(极少见但合法)
WHERE、跨表 UPDATE 或 AFTER 里的 INSERT SELECT,就可能在高峰时段把整张核心表拖进不可用状态。











