触发器在主从复制中不一致的核心是“执行结果是否可复现”,而非是否执行;row模式虽记录行变更,但uuid()、now()、@var等非确定性操作及definer权限、sql security、字符集等差异均会导致主从行为分裂。

触发器在主从复制中不一致,不是“会不会执行”的问题,而是“执行结果是否可复现”。ROW 模式能保证行变更被记录,但无法保证触发器内部逻辑在主从上产出相同结果——只要触发器里有 UUID()、NOW()、@var 或依赖 DEFINER 权限,就大概率裂开。
查 @@sql_log_bin 确认触发器是否真被跳过
从库上触发器“没生效”,往往不是被禁用,而是当前会话没写 binlog。登录从库直接执行:
SELECT @@sql_log_bin;
返回 0 表示该会话不记 binlog(但触发器仍可能执行);返回 1 才表示变更会被记录并参与复制。别只改配置文件,必须查运行时值。常见误判是看到 SHOW TRIGGERS 为空,就以为触发器没加载——其实可能是 DEFINER 用户不存在,导致加载失败,连元数据都不显示。
比对 SHOW CREATE TRIGGER 的三处关键差异
主从触发器定义哪怕一个字符不同,都可能导致行为分裂。分别在主库和从库执行:
SHOW CREATE TRIGGER trigger_name;
逐字比对以下三项:
-
DEFINER用户是否存在、是否有TRIGGER权限(从库缺用户 → 触发器加载失败 →SHOW TRIGGERS不显示) -
SQL SECURITY是DEFINER还是INVOKER(DEFINER下从库以定义者身份执行,权限不足则静默失败) - 字符集、
DELIMITER、注释内容是否完全一致(某些 MySQL 版本因注释换行或空格差异导致解析失败)
排查触发器里是否藏了非确定性函数
ROW 模式只记录最终行状态,不记录计算过程。哪怕主从 binlog_format 都是 ROW,以下写法仍必然不一致:
- 调用
UUID()、NOW()、SYS_DATE()、RAND()—— 主从本地生成,值天然不同 - 引用用户变量
@tmp或临时表 —— 从库无上下文,执行失败或跳过 - 嵌套存储过程中含
SELECT ... INTO或游标 ——ROW不捕获中间结果,只记最终 DML
正确做法是把这类逻辑提到应用层生成后再传入,或改用 NEW.field := DEFAULT(field) 让 MySQL 自动填充(前提是字段定义了 DEFAULT)。
验证 @@binlog_format 是否真正统一为 ROW
不能只信配置文件。主库和从库都必须执行:
SELECT @@binlog_format;
结果必须都是 'ROW'。若为 'STATEMENT' 或 'MIXED',触发器根本不会被复制或可能降级为 STATEMENT。改完后需确认所有从库已生效(重启或 SET GLOBAL binlog_format = 'ROW'),并观察 SHOW SLAVE STATUS\G 中的 Seconds_Behind_Master 是否突增——突增往往意味着触发器副作用开始暴露。
最隐蔽的坑是:即使所有配置都对,只要触发器里有一行 NEW.ts := NOW(),主从时间差几毫秒,就足以让业务逻辑分叉。这不是配置问题,是设计问题。











