MySQL UPDATE 语句详细执行流程
UPDATE 语句的详细执行流程
假设我们执行一条语句:UPDATE t SET c = c + 1 WHERE id = 10;
并且 id=10
这条记录存在。
开始事务(START TRANSACTION)
- 显式或隐式地开启一个事务。
查找记录(Find the Row)
- 服务器层命令解析后,进入存储引擎层。
- InnoDB 通过 B+ 树索引定位到
id=10
这行记录。
加锁阶段(Acquire Lock - 关键步骤!)
- 时机:在真正修改数据之前,InnoDB 会尝试为这行记录加上排他锁(X Lock)。
- 过程:
- 如果此时没有其他事务持有这行记录的锁(包括共享锁或排他锁),则加锁成功。
- 如果其他事务已经持有这行记录的锁(例如共享锁),那么当前事务会进入等待状态,直到超时或对方事务释放锁。
写入 undo log(Write Undo Log)
- 时机:在加锁成功之后,在修改内存数据之前。
- 目的:为了事务回滚和实现 MVCC(多版本并发控制)。
- 内容:将
id=10
这行记录修改前的内容(c
的旧值)拷贝到 undo log 中。这样如果事务回滚,就可以利用 undo log 将数据恢复原样。
修改内存数据页(Update Buffer Pool Data)
- 时机:在 undolog 写入之后。
- 过程:此时事务已经持有锁,并且回滚日志也已准备就绪。InnoDB 才放心地在内存中的 Buffer Pool 里将这条记录的
c
值更新为新值。注意:此时数据只是在内存中被修改,尚未刷回到磁盘的数据文件(.ibd)中。 这极大地提升了性能。
写入 redo log(Write Redo Log)
- 时机:在内存数据修改之后。
- 目的:为了保证事务的持久性(Durability)。
- 过程:将“在某个数据页上做了什么修改”这个操作记录到 redo log buffer 中。后续会有后台线程将其刷盘到 redo log 文件里。
- 注意:在事务提交时,通常需要强制将 redo log buffer 刷盘(
innodb_flush_log_at_trx_commit=1
),这是保证数据不丢失的关键。只要 redo log 落盘了,即使修改后的数据页还没来得及刷盘,数据库崩溃后也能通过 redo log 恢复。
事务提交(Commit)
- 执行
COMMIT
时,主要做两件事:- 将 redo log buffer 中与本事务相关的日志强制刷盘(fsync)。
- 写入一个特殊的提交标记到 redo log,表明事务已完成。
- 释放锁:在事务提交完成后,才会释放事务过程中持有的所有排他锁。这就是为什么我们说 InnoDB 默认遵守两阶段锁协议(2-Phase Locking, 2PL)——锁的加锁阶段分布在事务过程中,而释放锁的阶段集中在事务提交时。
- 执行
后续操作(Background Processes)
- 刷脏页:MySQL 有后台线程,会在合适的时机将 Buffer Pool 中已被修改的“脏页”刷新到磁盘的数据文件中。
- Purge:另一个后台线程会清理不再需要的旧版本的 undo log 记录。
总结与顺序关系
我们可以将这些操作串联起来,清晰地看到它们的先后顺序:
开始事务 → 定位数据 → [加锁] → 写undo log → 改内存数据 → 写redo log buffer → 提交(redo log刷盘) → [释放锁]
这个顺序设计保证了事务的 ACID 特性:
- 加锁保证了隔离性(Isolation)。
- 先写 undolog 保证了原子性(Atomicity)(可回滚)。
- 最后提交时 redolog 刷盘 保证了持久性(Durability)。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论