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 本身多复杂。
