statement模式下触发器必然双写或漏写,因只记录原始sql且从库重放时再次执行;row模式虽不重放触发器,但调用非确定函数、操作临时表或更新其他表仍导致主从不一致,根本解法是将逻辑移至应用层或改用generated column。

STATEMENT模式下触发器必然双写或漏写
MySQL 5.7 默认 binlog_format = STATEMENT,这种模式只记录原始 SQL(比如 INSERT INTO orders),不记录触发器内部逻辑。主库执行时触发器跑一次,从库重放这条语句时又触发一次——等于同一段逻辑被执行两遍。
常见现象包括:ERROR 1062 Duplicate entry、审计表多出重复行、updated_at 字段被覆盖成旧值。例如主库插入后触发器设 status = 'active',但从库回放时又执行一遍,可能被后续应用层 UPDATE 覆盖为 'pending'。
- 不能靠
SET SQL_LOG_BIN = 0在从库禁用触发器:这会让从库跳过 binlog 写入,但触发器仍可能运行,导致数据状态与主库语义脱节 - MIXED 模式也不安全:MySQL 在某些场景(如含函数的 INSERT SELECT)会自动降级到 STATEMENT,触发器照样双写
- 必须显式执行
SET GLOBAL binlog_format = 'ROW'并确认所有从库已同步该配置,否则切换无效
ROW模式下触发器仍可能破坏一致性
ROW 模式虽不重放触发器逻辑,但它只记录最终变更的行,无法保证触发器中调用的非确定性操作在从库产生相同结果。
只要触发器干了以下三件事之一,主从就大概率偏离:
- 调用
UUID()、RAND()、SYSDATE():这些函数每次执行返回新值,主从系统时间不同,SYSDATE()结果天然不一致 - 操作临时表或用户变量(如
@tmp_id):从库没有对应会话上下文,执行失败或静默跳过 - 更新其他表(如主表插入后往
audit_log写日志):ROW 日志只包含主表变更,audit_log的变更不会进 binlog,从库永远缺失
从库是否执行触发器取决于运行时配置,而非配置文件
SHOW CREATE TRIGGER 显示的 SQL SECURITY DEFINER 和当前会话的 @@sql_log_bin 值共同决定行为,静态配置容易误判。
例如从库上执行 SELECT @@sql_log_bin; 返回 1,说明当前会话会写 binlog,触发器默认执行;若返回 0,则不写 binlog,但触发器仍可能运行(除非权限不足或被 DROP 拦住)。
-
DEFINER用户必须在从库存在且拥有TRIGGER权限,否则SHOW TRIGGERS可能根本查不到该触发器 - 别只看 my.cnf 里的
binlog_format,运行时用SELECT @@binlog_format;确认才靠谱 - 5.7 不支持将外键级联上移到 SQL 层,因此触发器仍是唯一能模拟级联逻辑的方式——但这恰恰放大了复制风险
真正可靠的解法是移除触发器副作用
参数调优只能缓解表象,不能根治问题。只要触发器还依赖运行时环境或写其他表,主从偏差就是时间问题。
- 时间戳、唯一 ID 等字段由应用生成后传入,避免
NOW()、UUID()在从库重复计算 - 跨表更新(如订单+积分+通知)拆成应用内事务,用本地事件或消息队列保证最终一致性
- 简单字段补全(如
status根据created_at自动设为'pending')改用GENERATED COLUMN,不依赖运行时逻辑
ROW 模式只是让问题更隐蔽,而不是解决它。MySQL 5.7 的触发器在主从架构中本质是个“黑盒副作用”,越想在数据库层兜底,越容易掉进一致性陷阱。











