MySQL触发器不能被直接调用,所谓嵌套实为隐式触发:当前触发器执行DML操作(如INSERT INTO t2),若t2存在对应触发器,则被间接激活;但修改自身表默认不递归触发,除非开启innodb_recursive_triggers。

MySQL 触发器不能被“调用”,所以不存在“在触发器中调用另一个触发器”这回事。 所谓链式或嵌套,全是隐式触发:靠 DML 语句修改数据,间接激活目标表上的触发器。理解这点,才能避开绝大多数线上故障。
触发器之间没有 CALL 或 EXECUTE 语法
你写不出 CALL trigger_b(),也写不出 EXECUTE trigger_a —— MySQL 根本不提供触发器调用接口。TRIGGER 不是存储过程,不是函数,它没有执行入口。所有“触发器调用了另一个触发器”的说法,本质都是:当前触发器里执行了 INSERT/UPDATE/DELETE,恰好这些操作命中了其他表(或同一表但满足条件)上已定义的触发器。
- 错误写法示例:
CALL update_log_trigger()→ 直接报错ERROR 1305 (42000): PROCEDURE db.update_log_trigger does not exist - 正确路径:在
t1的AFTER INSERT触发器里写INSERT INTO t2 VALUES (...),而t2上恰好有AFTER INSERT触发器 → 它才会运行 - 哪怕你把两个触发器建在同一个库、同名、同事件类型,只要没实际 DML 操作,就绝不会“连起来”
跨表触发可行,但递归自触发默认禁止
一张表的触发器去改另一张表,只要那张表有对应触发器,就能链下去;但改回自己这张表,默认不会再次触发——这是 InnoDB 的硬性保护机制,防止无限循环。
- 例如:
t1的BEFORE UPDATE触发器里执行UPDATE t1 SET status = 'done' WHERE id = NEW.id→ 这条语句不会再次触发t1上任何触发器(除非显式开启innodb_recursive_triggers = ON) - 而如果改成
UPDATE t2 SET ...,且t2有AFTER UPDATE触发器 → 它会被激活 - 注意:
innodb_recursive_triggers是全局变量,设为ON后,同一语句内对同表的多次修改可能引发多层递归,极易踩到max_sp_recursion_depth限制(默认 6),报错ERROR 1456 (HY000)
调试时最常误判的三种“嵌套”假象
很多 DBA 看到日志里出现多次触发记录,第一反应是“触发器嵌套了”,其实大概率是以下情况之一:
-
批量操作按行触发:一条
INSERT INTO t1 SELECT * FROM t2影响 100 行 → 触发器执行 100 次,不是嵌套,是正常行为 -
主从复制二次执行:主库写入触发一次,binlog 传到从库再重放,又触发一次 → 查看
sql_log_bin状态,或在从库临时设SET sql_log_bin = OFF测试 - 触发器内调用存储过程,过程里又写同表:表面看是“触发器调用过程”,实际仍是“过程执行 DML → 触发器响应”,但调用链变长,出问题时难以定位源头
真正需要警惕的循环依赖
当 A → B → C → A 这种环路存在时,MySQL 不会提前拦截,而是等到栈溢出才报错。线上环境很难复现,日志只显示 Recursion depth exceeded,没有完整路径。
- 排查手段只有:
SELECT * FROM information_schema.TRIGGERS WHERE EVENT_OBJECT_TABLE IN ('a','b','c')手动梳理每张表上有哪些触发器、它们改了哪些表 - 避免方案:用临时表中转,比如 A 触发器不直接 UPDATE B,而是
INSERT INTO tmp_sync SELECT ... FROM A,再由定时任务或应用层读取tmp_sync去更新 B - 特别注意:FEDERATED 表或跨库触发器(如
db1.t1触发db2.t2)也会进入该链条,但依赖外部连接稳定性,失败时更难诊断
真正复杂的点从来不在语法能不能写,而在于:你写的那条 INSERT,到底会悄悄唤醒多少个沉睡的触发器;而它们各自又写了什么,会不会反过来咬你一口。上线前必须人工画出 DML 路径图,而不是依赖“应该没问题”的直觉。


















