mysql 8.0建议使用row格式,因其避免主从不一致、支撑cdc与审计回滚,并强制拦截非确定性statement语句;升级后需显式配置binlog_format=row并重启,主从必须严格一致。

MySQL 8.0 存储过程中非确定性语句在 STATEMENT 下会直接报错
MySQL 8.0 对复制安全性的校验更严格,STATEMENT 格式下,只要存储过程里包含 NOW()、UUID()、SYSDATE()、用户变量(@var)、临时表操作等非确定性行为,主库写 binlog 时就会拒绝记录,并抛出错误:Cannot execute statement: binlogging impossible。这不是警告,是硬性中断——事务直接回滚,应用看到的是 SQL 执行失败。
- 哪怕只有一行
SET @ts = NOW();,整个存储过程在binlog_format = STATEMENT下就无法执行 -
MIXED也不能绕过:它会在运行时尝试降级为ROW,但若配置未显式允许或权限受限,仍会卡在 same error - DDL(如
CREATE TABLE)本身不受影响,但 DML 部分一旦混入非确定逻辑,就触发拦截
ROW 格式让存储过程的 DML 操作可复制且行为一致
ROW 不依赖语句上下文,只记录“哪几行被改成了什么样”,所以存储过程内部怎么算时间、怎么拼接字符串、怎么循环更新,对 binlog 来说都不重要——它只忠实记录最终数据变更。从库重放时,不重新执行过程逻辑,而是直接按日志 apply 行变更,彻底规避了主从结果不一致风险。
- 例如:存储过程里用
INSERT ... SELECT UUID(), ...,STATEMENT会让从库生成不同 UUID;ROW则把主库生成的真实值直接写进日志,从库照搬 - 注意:
ROW不记录过程本身调用,只记录其引发的 DML 事件(Write_rows_v1/Update_rows_v1),所以SHOW BINLOG EVENTS看不到CALL proc_name(),只看到后续的行变更 - 如果过程里有
SELECT ... INTO或游标遍历,只要没产生 DML,就不会产生 binlog 事件——这点和STATEMENT下“整条 CALL 语句被记”完全不同
升级到 8.0 后必须显式配置,不能靠默认或 SET GLOBAL
MySQL 5.7 升级到 8.0 后,binlog_format 默认值虽变成 MIXED,但该值仅在启动时读取配置文件;若 my.cnf 中没写 binlog-format = ROW,服务实际加载的仍是旧值(可能是 STATEMENT),且 SET GLOBAL binlog_format = 'ROW' 对已存在的连接无效,更不会影响 SQL 线程。
- 检查真实生效值只能用:
SELECT @@global.binlog_format;,别信配置文件里写了就算数 - 必须在
[mysqld]段添加binlog-format = ROW,并完整重启 MySQL 进程 - 主从必须同步配置:从库
binlog_format可设为OFF(不开启 binlog),但若开启,必须与主库完全一致,否则启动时报错:The slave is running with binlog_format = STATEMENT, but the master sent a ROW event
ROW 下要注意日志膨胀和主键约束
存储过程常批量操作,ROW 会为每行变更生成独立事件,尤其涉及大字段(TEXT/BLOB)或全表更新时,单个事务 binlog 可能达 MB 级,磁盘和网络压力陡增。
- 启用
binlog_row_image = MINIMAL(要求所有表有主键或唯一非空索引),可只记录变更列,大幅压缩体积 - 避免在过程里写
UPDATE t SET blob_col = ?这种全量更新;改为业务层拆成小批次,或用WHERE精确限定范围 - 用
mysqlbinlog --base64-output=DECODE-ROWS --verbose抽样检查日志内容,确认是否出现大量Write_rows_v1事件
真正麻烦的不是选 ROW,而是选了之后没配 binlog_row_image、没加主键、没控制批量粒度——这些细节一漏,日志就撑爆磁盘,比格式选错还快。











