直接在触发器里改自己监听的表必然出问题:mysql报error 1442硬拦截,sql server静默递归致卡死,postgresql默认放行但需pg_trigger_depth()>1手动截断;数据库级开关如recursive_triggers对自引用无效,唯一可靠解法是运行时标记(如context_info)或深度控制。

直接在触发器里改自己监听的表,就一定会出问题——MySQL 报 ERROR 1442,SQL Server 静默递归卡死,PostgreSQL 默认放行但必须手动拦。靠数据库开关(比如 RECURSIVE_TRIGGERS)基本无效,真正能落地的解法是运行时标记或深度控制。
MySQL:别碰原表,用 SET NEW.col 或中间表
MySQL 对触发器自更新是语法级拦截,不是性能问题,而是直接拒绝执行。哪怕你加了 IF OLD.status != NEW.status,只要触发器体里出现任何对本表的 UPDATE/INSERT/DELETE,就会触发 ERROR 1442。
-
BEFORE触发器优先用SET NEW.status = 'done'修改即将写入的值,而不是再发一条UPDATE -
AFTER里真要落库(比如记日志+同步状态),必须拆到中间表:INSERT INTO trigger_queue (table_name, pk, action) VALUES ('users', NEW.id, 'update') - 跨库操作不等于安全——
UPDATE other_db.users如果目标库该表也有触发器,照样递归 - 调试时别依赖日志表写入是否成功,先确认
log_warnings = 2和log_error路径是否可写
SQL Server:用 CONTEXT_INFO 做会话级标记
@@NESTLEVEL 不可靠,DISABLE TRIGGER 有并发风险,RECURSIVE_TRIGGERS OFF 对自引用完全没用。唯一轻量、隔离、无需建表的方案是 CONTEXT_INFO。
- 业务 SQL 执行前设标记:
SET CONTEXT_INFO 0x54524947474552(即 "TRIGGER" 的 ASCII 十六进制) - 触发器开头立即检查:
IF CONTEXT_INFO() = 0x54524947474552 RETURN - 避免用
CONVERT(VARCHAR, CONTEXT_INFO())比较——编码或截断会导致失效 - 若触发器被存储过程调用,过程开头设标记、结尾清空:
SET CONTEXT_INFO 0x0
PostgreSQL:靠 pg_trigger_depth() 主动截断
PostgreSQL 不禁止自更新,但每次都会再触发,所以必须自己判断层级。它提供精准函数,但容易漏掉 TG_OP 组合校验。
- 触发器开头加:
IF pg_trigger_depth() > 1 THEN RETURN; END IF; - 更精细控制可结合:
IF pg_trigger_depth() > 1 AND TG_OP = 'UPDATE' THEN RETURN; - 不要只信深度判断——如果触发器调用了函数,而函数里又间接更新了本表,仍可能绕过
-
pg_trigger_depth()返回的是当前触发器嵌套层数,首次为 1,第二次为 2,以此类推
恢复数据时的特殊坑:sql_log_bin = 0 + 守卫变量
mysqldump 恢复卡住,99% 是触发器报 ERROR 1442 后静默失败,不是循环,是拦截。删触发器风险太大,临时禁用才是正解。
- 恢复前执行:
SET SESSION sql_log_bin = 0;和SET @TRIGGER_DISABLED = 1; - 所有触发器开头统一加守卫:
IF @TRIGGER_DISABLED IS NOT NULL THEN LEAVE proc_label; - 别用
max_sp_recursion_depth = 1应对恢复场景——它管不住ERROR 1442,只管递归深度 - 批量
INSERT时触发器逐行执行,I/O 和锁开销会指数放大,大表恢复务必提前处理触发器逻辑
最易被忽略的一点:所有方案都依赖“触发器被调用前,标记/设置/判断必须已生效”。业务代码漏设 CONTEXT_INFO、连接池复用未重置 max_sp_recursion_depth、恢复脚本忘了 SET SESSION——这些才是线上出问题的真实原因。











