MySQL执行Update字段值未变,存储引擎去重更新流是否记录日志?
- 内容介绍
- 文章标签
- 相关推荐
本文共计804个文字,预计阅读时间需要4分钟。
不会写binlog(默认配置下)。在执行UPDATE时,MySQL会先读取原始值,再比较新旧值;如果所有被更新的字段值与原值完全一致(类型、NULL属性、字节级内容均相同),则Server层会跳过实际更新操作,自然也不会触发binlog写入。但这个行为会受到存储引擎和SQL模式的影响,不能一概而论。
InnoDB 对“无变化更新”的处理:不修改页、不刷脏、不记 undo log
InnoDB 在执行 UPDATE 前会调用 row_upd_changes_to_persistent() 判断是否真有变更。关键点如下:
- 比较基于字段的内部存储格式(例如
TIMESTAMP会转为秒级整数再比,VARCHAR区分尾部空格与否取决于collation) - 即使 SQL 写了
SET name = name,只要该列值未变,InnoDB 不会标记记录为“已修改”,对应的数据页不会变脏,innodb_buffer_pool_pages_dirty不增 - 不生成新的 undo log 版本,MVCC 快照不受影响
- 但会加 X 锁(或意向锁)并立即释放——锁开销仍在,只是没持久化动作
binlog 写入时机由 server 层控制,但受 storage engine 返回结果驱动
MySQL Server 层在调用 handler::update_row() 后,检查返回值:HA_ERR_RECORD_IS_THE_SAME 表示引擎明确告知“没变”,此时 Query_log_event 不生成。但要注意:
- 若开启
binlog_row_image = MINIMAL,且某列未出现在 WHERE 或 SET 中,InnoDB 可能无法准确判断是否变更(尤其涉及函数或表达式时) -
sql_mode含STRICT_TRANS_TABLES或ERROR_FOR_DIVISION_BY_ZERO时,某些隐式转换可能让“看似相等”的值实际触发更新(如'1' = 1在非严格模式下成立,但更新INT字段赋字符串可能触发截断+重写) - 使用
INSERT ... ON DUPLICATE KEY UPDATE时,即使最终 SET 值未变,只要发生冲突检测并进入 UPDATE 分支,仍会走完整更新流程(含 binlog)
实操验证方法:看 status、binlog 和 InnoDB 状态变量
快速确认某条 UPDATE 是否真实生效,别只信返回的 “0 rows affected”:
- 执行前后查
SHOW STATUS LIKE 'Innodb_buffer_pool_pages_dirty'—— 不变说明没刷脏 - 用
mysqlbinlog --base64-output=DECODE-ROWS -v检查对应事务是否含Table_map+Update_rows事件 - 开启
performance_schema并查events_statements_history_long,看ROWS_AFFECTED和NESTING_EVENT_TYPE - 注意:客户端显示 “0 rows affected” 是 server 层统计,不代表引擎层没做任何事(比如锁等待、解析、权限校验都已发生)
真正容易被忽略的是隐式类型转换和 collation 影响下的“逻辑相等 ≠ 存储相等”。比如 utf8mb4_0900_as_cs 下 'a ' 和 'a' 不等,但 utf8mb4_general_ci 下可能被视作相同——这种差异会让同一句 SQL 在不同库上产生截然不同的日志行为。
本文共计804个文字,预计阅读时间需要4分钟。
不会写binlog(默认配置下)。在执行UPDATE时,MySQL会先读取原始值,再比较新旧值;如果所有被更新的字段值与原值完全一致(类型、NULL属性、字节级内容均相同),则Server层会跳过实际更新操作,自然也不会触发binlog写入。但这个行为会受到存储引擎和SQL模式的影响,不能一概而论。
InnoDB 对“无变化更新”的处理:不修改页、不刷脏、不记 undo log
InnoDB 在执行 UPDATE 前会调用 row_upd_changes_to_persistent() 判断是否真有变更。关键点如下:
- 比较基于字段的内部存储格式(例如
TIMESTAMP会转为秒级整数再比,VARCHAR区分尾部空格与否取决于collation) - 即使 SQL 写了
SET name = name,只要该列值未变,InnoDB 不会标记记录为“已修改”,对应的数据页不会变脏,innodb_buffer_pool_pages_dirty不增 - 不生成新的 undo log 版本,MVCC 快照不受影响
- 但会加 X 锁(或意向锁)并立即释放——锁开销仍在,只是没持久化动作
binlog 写入时机由 server 层控制,但受 storage engine 返回结果驱动
MySQL Server 层在调用 handler::update_row() 后,检查返回值:HA_ERR_RECORD_IS_THE_SAME 表示引擎明确告知“没变”,此时 Query_log_event 不生成。但要注意:
- 若开启
binlog_row_image = MINIMAL,且某列未出现在 WHERE 或 SET 中,InnoDB 可能无法准确判断是否变更(尤其涉及函数或表达式时) -
sql_mode含STRICT_TRANS_TABLES或ERROR_FOR_DIVISION_BY_ZERO时,某些隐式转换可能让“看似相等”的值实际触发更新(如'1' = 1在非严格模式下成立,但更新INT字段赋字符串可能触发截断+重写) - 使用
INSERT ... ON DUPLICATE KEY UPDATE时,即使最终 SET 值未变,只要发生冲突检测并进入 UPDATE 分支,仍会走完整更新流程(含 binlog)
实操验证方法:看 status、binlog 和 InnoDB 状态变量
快速确认某条 UPDATE 是否真实生效,别只信返回的 “0 rows affected”:
- 执行前后查
SHOW STATUS LIKE 'Innodb_buffer_pool_pages_dirty'—— 不变说明没刷脏 - 用
mysqlbinlog --base64-output=DECODE-ROWS -v检查对应事务是否含Table_map+Update_rows事件 - 开启
performance_schema并查events_statements_history_long,看ROWS_AFFECTED和NESTING_EVENT_TYPE - 注意:客户端显示 “0 rows affected” 是 server 层统计,不代表引擎层没做任何事(比如锁等待、解析、权限校验都已发生)
真正容易被忽略的是隐式类型转换和 collation 影响下的“逻辑相等 ≠ 存储相等”。比如 utf8mb4_0900_as_cs 下 'a ' 和 'a' 不等,但 utf8mb4_general_ci 下可能被视作相同——这种差异会让同一句 SQL 在不同库上产生截然不同的日志行为。

