MySQL UPDATE执行“读–改–写”路径,受隔离级别、索引、主键变更等影响;无索引WHERE触发全表扫描与next-key锁;主键更新实为删+插;binlog与redo log通过两阶段提交保障crash-safe。

UPDATE 语句执行时,MySQL 真实走的是一条“读–改–写”路径
不是直接在磁盘上改数据页,而是先定位、加锁、读取旧值到内存,再计算新值,最后写入变更(可能缓存在 buffer pool,也可能刷盘)。整个过程受事务隔离级别、索引是否存在、是否涉及主键/唯一键等关键因素影响。
WHERE 条件无索引时,会触发全表扫描 + 行级锁升级为表级锁风险
如果 UPDATE 的 WHERE 子句无法命中索引(比如对字段做函数操作:WHERE YEAR(create_time) = 2024),InnoDB 会逐行扫描聚簇索引,并对**所有扫描过的记录加 next-key lock**。更危险的是,在 READ-COMMITTED 以下隔离级别,这些锁可能持续到事务结束,极易引发锁等待甚至死锁。
- 务必用
EXPLAIN检查UPDATE对应的SELECT是否走了索引 - 避免在
WHERE中对索引列使用函数、类型隐式转换(如WHERE user_id = '123'而user_id是INT) - 批量更新尽量拆成小事务,单次影响行数控制在 1000 行以内
UPDATE 涉及主键或唯一索引变更时,实际是“删除+插入”两步
当 UPDATE 修改了主键(id)或任意唯一索引列的值,InnoDB 不会原地更新索引项,而是:先按旧主键值定位并标记删除,再用新主键值插入一条新记录。这会导致:
- undo log 体积翻倍(旧记录要可回滚,新记录要可回滚)
- 自增主键间隙可能被浪费(即使新值不冲突)
- 如果新值违反唯一约束,报错是
ERROR 1062 (23000): Duplicate entry 'X' for key 'PRIMARY',但此时旧记录已逻辑删除,需回滚才能恢复
UPDATE users SET id = 999 WHERE id = 1;
binlog 和 redo log 如何协同保证 crash-safe
MySQL 启用 innodb_flush_log_at_trx_commit=1 和 sync_binlog=1 时,一个成功提交的 UPDATE 必然同时落盘:redo log 刷盘确保崩溃后能前滚恢复,binlog 刷盘确保主从复制不丢数据。但顺序不可乱——必须先写 redo log 并 flush,再写 binlog 并 flush,最后才 commit。否则 crash 可能导致主从不一致。
这个两阶段提交(2PC)流程中,最容易被忽略的是:如果 binlog 写成功但 commit 失败,MySQL 重启后会根据 binlog 补 recover;但如果 redo log 写失败,事务直接 abort,binlog 也不会写入——所以 redo log 是第一道生死线。










