mysql update执行流程为:连接→解析→权限校验→生成执行计划→引擎定位→加锁→内存修改→redo log(prepare)→binlog→redo log(commit)→刷脏页;锁行为由引擎与隔离级别共同决定,两阶段提交保障crash safe。

MySQL UPDATE 语句的执行流程从连接到落盘
MySQL 执行一条 UPDATE 不是简单地“改一行”,而是一套涉及连接、解析、优化、加锁、修改、写日志、刷盘的完整链路。实际执行中,哪一步卡住、慢或失败,直接决定你看到的是“秒回”还是“锁表半小时”。
典型路径是:客户端连接 → 查询缓存(已弃用,8.0 移除)→ 解析 SQL → 权限校验 → 生成执行计划 → 存储引擎层定位记录 → 加锁(行锁/间隙锁/临键锁)→ 修改内存中的数据页 → 写入 redo log(prepare)→ 写入 binlog → redo log commit → 刷脏页(异步)。
为什么 UPDATE 会锁表或锁住不相关的行
锁行为完全由存储引擎和隔离级别共同决定,InnoDB 下尤其容易踩坑。不是“有没有 WHERE 就锁全表”,而是“是否命中索引 + 隔离级别 + WHERE 条件能否使用索引下推”共同作用的结果。
- 没走索引的
WHERE条件(如WHERE name LIKE '%abc'),InnoDB 可能退化为聚簇索引全扫描,对所有扫描到的记录加临键锁(Next-Key Lock) -
REPEATABLE READ下,即使只更新一行,也可能因间隙锁(Gap Lock)阻塞其他事务在相同间隙插入 - 唯一索引等值查询(
WHERE id = 100)只加行锁;范围查询(WHERE id > 100)则加临键锁,覆盖记录+间隙 - 显式事务中未及时
COMMIT,锁会一直持有,导致后续语句等待甚至Lock wait timeout exceeded
redo log 和 binlog 怎么协同保证 crash safe
崩溃恢复靠的是两阶段提交(2PC):redo log 负责物理页变更的持久化,binlog 负责逻辑操作的归档与主从同步。两者必须一致,否则主从数据错乱或重启后数据丢失。
关键步骤顺序不可颠倒:innodb_prepare → write binlog → innodb_commit。如果 crash 发生在写完 binlog 但未 commit redo log 的中间状态,MySQL 启动时会回滚该事务;如果 redo log 已 commit 但 binlog 写失败,则依赖 binlog_checksum 和人工核对补救。
注意:sync_binlog=1 和 innodb_flush_log_at_trx_commit=1 是强一致性前提,但会明显降低写性能。
UPDATE 很慢?先看执行计划和锁等待,别急着加索引
慢 UPDATE 常见原因不是 SQL 写得差,而是隐性资源争抢。先查 SHOW ENGINE INNODB STATUS\G 看 TRANSACTIONS 和 LATEST DETECTED DEADLOCK,再结合 SELECT * FROM performance_schema.data_locks(8.0+)确认锁对象。
- 执行计划显示
type: ALL或rows极大 → 索引缺失或失效(如对字段做函数操作:WHERE YEAR(create_time) = 2024) -
state: Updating持续很久 → 可能被其他事务锁住,查information_schema.INNODB_TRX找 blocking_trx_id - 大量
innodb_row_lock_waits→ 锁冲突高,考虑拆分事务、调整隔离级别(如降为READ COMMITTED关闭间隙锁)、或业务层重试 - 频繁 update 同一热点行(如计数器)→ 考虑用
INSERT ... ON DUPLICATE KEY UPDATE或应用层批量合并
真正影响 UPDATE 效率的,往往是锁等待时间和刷脏页压力,而不是 SQL 本身多复杂。










