直接结论:触发器导致主从不一致,90%以上是binlog_format没设成row或主从未同步生效;剩下10%多因触发器调用uuid()、now()等非确定性函数。statement模式只记录原始sql,不记录触发器执行逻辑,从库重放时因权限、会话变量、时间函数等差异导致结果不同;必须主从均确认@@binlog_format='row',禁用sql_log_bin绕过和idempotent模式,并人工排查触发器中非确定性调用及定义一致性。

直接结论:触发器导致主从不一致,90%以上是 binlog_format 没设成 ROW,或虽设了但主库/从库没同步生效;剩下10%里,绝大多数出在触发器内部调用了 UUID()、NOW() 这类非确定性函数。
为什么 STATEMENT 模式下触发器必然不一致
STATEMENT 模式只记录原始 SQL,比如 INSERT INTO orders (...) VALUES (...),完全不记录触发器实际执行的后续变更。从库重放时,触发器逻辑可能因以下原因失效或结果不同:
-
SQL SECURITY DEFINER下,从库缺少定义者用户或权限,触发器静默跳过 - 触发器依赖
@user_var或临时表,这些在从库不存在 - 调用
USER()、CURRENT_USER()返回的是从库会话用户,不是主库值 - 使用
RAND()或SYS_DATE(),每次执行结果不同
确认并强制主从都用 ROW 模式
别信配置文件,运行时值才作数。必须分别在主库和从库执行:
SELECT @@binlog_format; —— 两处都必须返回 'ROW'
如果某端是 'STATEMENT' 或 'MIXED',立即处理:
- 修改
my.cnf中的binlog_format=ROW,然后重启 MySQL(推荐) - 或执行
SET GLOBAL binlog_format = 'ROW';,但注意:该设置只对新连接生效,已有复制线程仍用旧格式 - 检查是否被
SET SQL_LOG_BIN = 0绕过 —— 在主库执行SELECT @@sql_log_bin;,必须为1 - 确认从库未启用
slave_exec_mode=IDEMPOTENT(应设为STRICT),否则重复写入失败会被忽略,掩盖触发器未执行
检查触发器定义是否主从完全一致
即使开了 ROW,如果触发器本身没同步过去,或定义有细微差异,照样出问题。分别在主库和从库执行:
SHOW CREATE TRIGGER trigger_name;
逐字比对输出,重点看:
-
DEFINER用户是否存在?从库上执行SELECT COUNT(*) FROM mysql.user WHERE User = 'xxx';确认 -
SQL SECURITY是DEFINER还是INVOKER?DEFINER下从库会以定义者身份执行,权限不足就失败 - 字符集声明(如
CHARACTER SET utf8mb4)、DELIMITER、注释内容是否完全相同?某些 MySQL 版本会因注释空格差异拒绝加载
排查触发器内非确定性逻辑
ROW 模式只记录最终行变更,不保证函数在从库重放时结果一致。人工扫描触发器体:
SHOW CREATE TRIGGER xxx; 后搜索关键词:UUID(、NOW(、SYS_DATE(、RAND(、@、TEMPORARY
发现就改掉:
- 把
NEW.id := UUID();改成应用层生成后传入,或用NEW.id := DEFAULT(id);(前提是字段有默认值) - 把
NEW.created_at := NOW();改成NEW.created_at := DEFAULT(created_at);或由应用传时间戳 - 避免在触发器里查临时表、用户变量、调用含
SELECT ... INTO的存储过程
最易被忽略的一点:MySQL 9.6.0 起,外键级联已上移到 SQL 层并完整记入 binlog,但你用的很可能还是旧版本——别指望外键能替代触发器做一致性保障,该查还得查。











