MySQL的UPDATE与SELECT执行流程本质不同:UPDATE需加行锁、写undo log和redo log以保证原子性,而SELECT(默认)仅读快照、不加锁不写日志;无索引WHERE会导致全表锁,引发死锁;优化器可能将INSERT ON DUPLICATE KEY UPDATE等改写为单次引擎操作,避免应用层竞态。

MySQL 更新语句和查询语句的执行流程本质不同
不一样。虽然都走「连接→解析→优化→执行」的大框架,但 UPDATE 和 SELECT 在优化器决策、存储引擎交互、锁机制、日志写入等关键环节存在根本性差异。不能简单认为“只是 SQL 类型不同”。
UPDATE 会触发行锁、redo log 和 undo log,SELECT(默认)不会
这是最常被忽略的性能与一致性根源。UPDATE 必须保证原子性和可回滚性,因此 InnoDB 会:
- 对匹配的每一行加
X 锁(或next-key 锁,取决于隔离级别和索引),阻塞其他事务的写和部分读 - 先写
undo log,用于 rollback 或 MVCC 版本链构建 - 再修改内存中的数据页,并写入
redo log buffer(刷盘策略由innodb_flush_log_at_trx_commit控制) - 而普通
SELECT(非SELECT ... FOR UPDATE或LOCK IN SHARE MODE)只读取快照版本,不加锁、不写任何 log
UPDATE 的 WHERE 条件是否命中索引,直接影响锁范围和性能
没索引的 UPDATE 是灾难性的。它会导致全表扫描 + 全表加锁(在 RR 隔离级别下是 gap lock 或 next-key lock),极易引发死锁和长事务阻塞。
UPDATE users SET status = 'done' WHERE create_time < '2023-01-01'; -- 如果 create_time 无索引,危险! UPDATE users SET status = 'done' WHERE id = 123; -- id 是主键,仅锁该行,安全
对比之下,SELECT 即使没索引,最多慢一点,不会锁表或拖垮并发。
UPDATE 可能被优化器改写为“先查后更”,但不是 SELECT + INSERT 的简单组合
比如 INSERT ... ON DUPLICATE KEY UPDATE 或 REPLACE INTO,表面像“查+更”,实际由存储引擎在一次操作中完成唯一键检查与行变更,避免了用户态的两次网络往返和中间状态暴露。而手动拆成 SELECT 再 UPDATE,不仅慢,还会因并发导致竞态(如 A 查到存在,B 同时删掉该行,A 接着 UPDATE 就失效)。
真正要复用查询逻辑做更新时,优先考虑 UPDATE ... JOIN 或子查询形式,让优化器在服务端完成关联判断,而不是靠应用层协调。
UPDATE 流程里藏着太多隐式动作:锁、日志、唯一性校验、外键检查……这些都不是 SELECT 要操心的。别因为语法结构相似,就低估 UPDATE 的重量级行为。
