默认情况下,update设置相同值不会产生binlog记录,但前提是binlog_format=row且binlog_row_image=full;若为statement模式则必然写binlog,minimal模式可能因未读取全部字段而误判变更,on duplicate key update即使值相同也必写binlog。

默认情况下,UPDATE 设置相同值**不会**产生 binlog 记录——但这个“默认”有严格前提。是否写 binlog,取决于 binlog_format、binlog_row_image、SQL 模式、存储引擎返回值等多重条件,稍有偏差就可能打破“去重更新”的预期。
binlog_format = STATEMENT 时,相同值 UPDATE 必然写 binlog
在 STATEMENT 模式下,MySQL 不比较行内容,只记录原始 SQL 字符串。哪怕你执行 UPDATE t SET name = 'alice' WHERE id = 1,而当前 name 就是 'alice',server 层也照单全收,直接生成 Query_log_event 写入 binlog。
- 不依赖 InnoDB 是否真修改数据,也不检查
HA_ERR_RECORD_IS_THE_SAME返回值 - 主从复制时,这条语句会在从库重新执行,可能触发锁、触发器、甚至因隐式类型转换导致实际变更(如
SET c = '1'更新INT列) -
sql_mode含STRICT_TRANS_TABLES时更危险:看似相等的字符串和数字可能因校验失败转为 0 或报错,反而“激活”更新流程
binlog_format = ROW 且 binlog_row_image = MINIMAL 时,可能漏判“无变更”
MINIMAL 模式只记录前镜像中能唯一标识行的列(如主键 + 唯一键),以及后镜像中被 SQL 显式指定的列。如果 UPDATE 中未包含某字段,InnoDB 就无法比对它的新旧值。
- 例如表
t(id PK, a INT, b TEXT),执行UPDATE t SET a = 5 WHERE id = 1,b列不在 SET 子句中 → InnoDB 不读取b的当前值 → 无法判断整行是否“真正未变” - 此时即使
a值本就为 5,InnoDB 可能仍走完整更新路径(加锁、标记脏页、生成 undo),server 层收到非HA_ERR_RECORD_IS_THE_SAME返回,就会写 binlog -
FULL模式可避免此问题:它强制读取所有列,确保比对完整
INSERT ... ON DUPLICATE KEY UPDATE 是个特例
哪怕最终 SET 的值与原值完全一致,只要发生唯一键/主键冲突并进入 UPDATE 分支,MySQL 就会执行完整更新逻辑。
- 冲突检测本身不等于“无变更判断”,InnoDB 仍会调用
update_row(),server 层收到的是“已执行更新”的结果,而非HA_ERR_RECORD_IS_THE_SAME - 因此 binlog 中必然出现
Update_rows_event(ROW 模式)或对应 SQL(STATEMENT 模式) - 这种行为与普通
UPDATE完全不同,不能套用“相同值不写日志”的经验
如何快速验证某条 UPDATE 是否真实变更了数据
别信 Rows matched: 1 Changed: 0 这类客户端反馈——它由 server 层根据引擎返回值推断,但某些中间件或驱动可能丢弃该信息。最可靠的方式是查状态变量和 binlog 位点:
- 执行前后对比
SHOW MASTER STATUS的Position值:没变 → 未写 binlog;变了 → 已写 - 观察
Innodb_buffer_pool_pages_dirty:不变 → InnoDB 未标记页为脏;上涨 → 真实修改发生 - 用
mysqlbinlog直接解析最新 binlog 文件,搜索对应表名和时间戳,确认事件是否存在 - 注意:
Handler_update状态变量在“无变更 UPDATE”时仍会 +1,它统计的是 update 操作调用次数,不是实际变更次数
真正决定“是否写 binlog”的,是 server 层拿到的存储引擎返回码,而不是你的肉眼判断或 SQL 表面含义。很多线上问题都出在 binlog_row_image 配置被忽略,或者误以为 ON DUPLICATE KEY UPDATE 和普通 UPDATE 行为一致——这些细节一旦错配,binlog 就会悄悄膨胀,主从延迟也可能因此恶化。











