UPDATE 语句的详细执行流程

假设我们执行一条语句:UPDATE t SET c = c + 1 WHERE id = 10; 并且 id=10 这条记录存在。

  1. 开始事务(START TRANSACTION)

    • 显式或隐式地开启一个事务。
  2. 查找记录(Find the Row)

    • 服务器层命令解析后,进入存储引擎层。
    • InnoDB 通过 B+ 树索引定位到 id=10 这行记录。
  3. 加锁阶段(Acquire Lock - 关键步骤!)

    • 时机:在真正修改数据之前,InnoDB 会尝试为这行记录加上排他锁(X Lock)
    • 过程
      • 如果此时没有其他事务持有这行记录的锁(包括共享锁或排他锁),则加锁成功。
      • 如果其他事务已经持有这行记录的锁(例如共享锁),那么当前事务会进入等待状态,直到超时或对方事务释放锁。
  4. 写入 undo log(Write Undo Log)

    • 时机在加锁成功之后,在修改内存数据之前。
    • 目的:为了事务回滚和实现 MVCC(多版本并发控制)。
    • 内容:将 id=10 这行记录修改的内容(c 的旧值)拷贝到 undo log 中。这样如果事务回滚,就可以利用 undo log 将数据恢复原样。
  5. 修改内存数据页(Update Buffer Pool Data)

    • 时机:在 undolog 写入之后。
    • 过程:此时事务已经持有锁,并且回滚日志也已准备就绪。InnoDB 才放心地在内存中的 Buffer Pool 里将这条记录的 c 值更新为新值。注意:此时数据只是在内存中被修改,尚未刷回到磁盘的数据文件(.ibd)中。 这极大地提升了性能。
  6. 写入 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 恢复。
  7. 事务提交(Commit)

    • 执行 COMMIT 时,主要做两件事:
      • 将 redo log buffer 中与本事务相关的日志强制刷盘(fsync)。
      • 写入一个特殊的提交标记到 redo log,表明事务已完成。
    • 释放锁在事务提交完成后,才会释放事务过程中持有的所有排他锁。这就是为什么我们说 InnoDB 默认遵守两阶段锁协议(2-Phase Locking, 2PL)——锁的加锁阶段分布在事务过程中,而释放锁的阶段集中在事务提交时。
  8. 后续操作(Background Processes)

    • 刷脏页:MySQL 有后台线程,会在合适的时机将 Buffer Pool 中已被修改的“脏页”刷新到磁盘的数据文件中。
    • Purge:另一个后台线程会清理不再需要的旧版本的 undo log 记录。

总结与顺序关系

我们可以将这些操作串联起来,清晰地看到它们的先后顺序:

开始事务 → 定位数据 → [加锁] → 写undo log → 改内存数据 → 写redo log buffer → 提交(redo log刷盘) → [释放锁]

这个顺序设计保证了事务的 ACID 特性:

  • 加锁保证了隔离性(Isolation)
  • 先写 undolog 保证了原子性(Atomicity)(可回滚)。
  • 最后提交时 redolog 刷盘 保证了持久性(Durability)