update本质是“读-算-写”三步闭环:必须先定位并读取原行,才能计算新值、加锁、写入undo log和buffer pool脏页,即使set常量值也需读取校验行存在性与可见性。

UPDATE必须先读取才能计算新值
因为SET子句里的表达式往往依赖原值,比如age = age + 1、status = CASE WHEN score > 90 THEN 'A' ELSE status END。MySQL没法凭空知道“当前age是多少”,必须先定位并读取原始行,才能执行计算。这和INSERT直接写入新值、DELETE只需标记删除不同——UPDATE本质是“读-算-写”三步闭环。
加锁发生在读取阶段,不是写入之后
InnoDB的更新流程强制要求“先加锁、再读取、再修改”,这是防止并发脏写的硬性约束。如果允许先写后读或边写边读,两个事务可能同时读到旧值、各自加1后再写回,导致最终只+1而非+2。所以WHERE id = 100命中行后,立刻对该行(及可能的间隙)加上X锁,后续所有读写都被阻塞,直到本事务提交或回滚。
Undo Log生成依赖原始数据快照
为了支持事务回滚和MVCC快照读,InnoDB必须在修改前把整行旧数据写入undo log。这个动作需要完整原始记录:包括所有字段值、事务ID、DB_ROLL_PTR等元信息。没有读取,就无法构造undo日志;没有undo日志,崩溃后无法回滚,其他事务也无法读到一致性视图。
Buffer Pool脏页机制决定了读写分离
MySQL不会直接改磁盘文件,而是先加载数据页到Buffer Pool内存中修改,形成“脏页”。但加载的前提是——得知道要加载哪一页。这就又回到起点:必须先根据WHERE条件走索引或扫描,定位物理位置,触发页加载。如果跳过读取,连该改哪块内存都不知道。
真正容易被忽略的是:哪怕你写的是UPDATE t SET x = 1 WHERE id = 1这种看似“不依赖原值”的语句,MySQL依然会完整读取整行。因为它要校验行是否存在、是否被其他事务删除(通过隐藏列DB_TRX_ID)、是否满足隔离级别可见性规则——这些都藏在读取后的行数据结构里,不是靠简单判断就能绕过的。











