必须将binlog_format设为ROW且binlog_row_image设为FULL,否则主从复制易失败、数据不一致;需动态验证、强制连接刷新并检查旧日志兜底。

直接结论:别修,换掉。STATEMENT 格式在现代 MySQL 主从架构里不是“可用但需小心”,而是“默认不安全”。排查只是确认它是不是罪魁祸首,改配置才是唯一靠谱动作。
怎么快速确认是不是 STATEMENT 在作祟
别只信配置文件或 SHOW VARIABLES LIKE 'binlog_format',它可能被动态改过但旧连接没刷新:
- 用业务账号连上去执行
SELECT @@binlog_format;—— 这才是真实写入格式 - 查慢日志里有没有
UUID()、NOW()、SYS_DATE()、@var或子查询 UPDATE/DELETE —— 这些在 STATEMENT 下必翻车 - 看从库
SHOW SLAVE STATUS\G的Last_Error,如果出现 “Duplicate entry”、“Can't find record” 或 “Could not execute Write_rows event”,基本就是 STATEMENT 和实际 ROW 日志混杂了
为什么 MIXED 不是退路
MIXED 看起来聪明,但它在 MySQL 5.6 及更早版本里对非确定性函数识别能力很弱;5.7+ 虽有改进,但仍有盲区:
- 即使启用了 MIXED,
binlog_row_image默认是MINIMAL,UPDATE/DELETE 只记改了哪几个字段,丢了完整行镜像 →pt-table-checksum校验失败 - DDL + DML 混合操作(比如
ALTER TABLE后紧跟INSERT)仍可能 fallback 到 STATEMENT - GTID 模式下,STATEMENT 和 ROW 事务不能共存于同一 binlog 文件,IO 线程会直接报错
Got fatal error 1236
改 ROW+FULL 的实操要点
不是改完配置就完事,关键在“新连接生效”和“旧日志兜底”:
- 先在主库执行
SET GLOBAL binlog_format = 'ROW';,再立刻执行SET GLOBAL binlog_row_image = 'FULL'; - 强制所有应用重建数据库连接 ——
SHOW PROCESSLIST查 Sleep 状态的旧连接,Druid 要设removeAbandonedOnBorrow=true,HikariCP 配connection-test-query=SELECT 1 - 验证新连接:
mysql -u app -p -e "SELECT @@binlog_format, @@binlog_row_image;",结果必须是ROW和FULL - 注意:旧 binlog 仍按原格式记录,切换后至少观察一个完整业务周期(比如一天订单高峰),确认从库无报错、
Seconds_Behind_Master不持续增长
改完还出问题?回头检查这几个硬伤
ROW+FULL 解决了 80% 的不一致根源,但剩下 20% 往往卡在这些地方:
- 主库开了
sql_log_bin=0却没关 —— 检查所有运维脚本、备份工具、ETL 任务是否漏了这句 - 从库没设
read_only=ON,被人误写 —— 执行SELECT @@read_only;确认是 1 -
log_bin或binlog_format在配置文件里被注释或拼错 ——mysqld --verbose --help | grep -A 1 "log-bin"直接看启动时加载的值 - 主从 MySQL 版本差太多(比如 8.0 主 + 5.7 从)—— 高版本语法、默认
sql_mode、JSON 函数等低版本根本解析不了
真正麻烦的不是发现不一致,而是发现时 expire_logs_days 已过期、relay log 损坏、或者 GTID 已断链。这时候 ROW+FULL 不是“更好”,而是你唯一能靠日志还原数据的底线配置。


















