一条更新语句是如何执行的?
连接与验证
客户端应用程序(如你的代码、数据库管理工具)通过连接器与数据库服务器建立连接。
连接器验证用户名、密码、主机权限等。验证通过后,建立连接并管理连接状态。
解析与优化
解析器: 接收 SQL 语句
UPDATE table_name SET column1 = value1, ... WHERE condition;
。词法分析: 识别 SQL 中的关键字(
UPDATE
,SET
,WHERE
)、表名、列名、值、操作符等。语法分析: 检查 SQL 结构是否符合语法规则(例如,
SET
后面是否跟了列赋值,WHERE
条件是否合法)。如果语法错误,返回错误预处理器:
检查表名和列名是否存在。
解析
*
(如果使用)。检查用户是否有执行
UPDATE
操作的必要权限。展开视图(如果更新的是视图)。
优化器:
生成该
UPDATE
语句的多种可能的执行计划。计划的核心在于如何高效地定位到需要更新的行(WHERE
条件)。考虑因素包括:表大小、索引情况(是否有索引能快速定位行)、列统计信息等。
估算每种执行计划的成本(主要是 I/O 和 CPU 开销)。
选择它认为成本最低的执行计划。
关键决策点: 是使用主键索引、唯一索引、普通索引还是进行全表扫描来找到目标行?是否需要临时表?选择哪个索引效率最高?
执行器
根据优化器选定的执行计划进行操作。
调用存储引擎提供的接口,开始执行真正的数据更新。
存储引擎处理 (InnoDB 核心流程)
开启事务: InnoDB 默认在自动提交模式下,每条语句都是一个独立事务。如果显式开启了事务,则属于该事务的一部分。执行器会通知存储引擎开始一个新事务(或加入当前事务)。
定位数据:
根据执行计划,利用 B+树索引查找满足
WHERE
条件的行。如果
WHERE
条件涉及索引列,通常能快速定位到目标行所在的数据页。如果无合适索引,则可能需要进行全表扫描(效率较低)。
读取旧值与 Undo Log:
目的 1 (回滚): 如果事务需要回滚(
ROLLBACK
),可以利用 Undo Log 将数据恢复到修改前的状态。目的 2 (MVCC): 为其他正在进行的事务提供行数据的旧版本快照,保证它们能看到一致性视图。
读操作: 为了支持事务的隔离性(特别是
READ COMMITTED
及以上级别)和 MVCC,InnoDB 需要读取目标行当前的快照数据(可能是当前最新提交版本,也可能是符合当前事务隔离级别的可见版本)。写 Undo Log: 在修改数据之前,InnoDB 会先将目标行的旧版本数据(修改前的数据)写入 Undo Log。
修改数据页 (Buffer Pool):
将需要更新的行数据从磁盘加载到内存中的 Buffer Pool(如果还未在内存中)。
在内存中的 Buffer Pool 里,按照
SET
子句更新这些行的数据。此时磁盘上的数据并未改变。这些被修改的内存页称为 Dirty Page(脏页)。
写 Redo Log Buffer:
在修改 Buffer Pool 中的页之后,将描述这次物理修改操作的 Redo Log 记录写入内存中的 Redo Log Buffer。
Redo Log 记录的是物理操作(如“在某个数据页的某个偏移量修改了几个字节的数据”),而不是逻辑操作(
UPDATE ...
)。目的 (持久性): 保证即使数据库发生崩溃,重启后也能利用 Redo Log 将未刷新到磁盘的脏页数据恢复出来(Crash Recovery)。遵循 Write-Ahead Logging 原则:日志先于数据页落盘。
处理二级索引:
如果更新的列包含在二级索引中,需要同步更新这些索引。
InnoDB 使用 Change Buffer 来优化非唯一二级索引的更新操作(特别是当索引页不在 Buffer Pool 中时)。修改先记录在 Change Buffer,之后在合适的时机(如索引页被读取时、后台线程刷脏时)再合并到索引页。这减少了随机 I/O。
唯一索引的更新需要立即检查唯一性约束,因此无法使用 Change Buffer
提交事务 (关键的两阶段提交)
当执行
COMMIT
语句时(或自动提交模式下语句执行完成时),事务进入提交阶段。Prepare Phase:
将 Redo Log Buffer 中与该事务相关的所有 Redo Log 记录刷盘(持久化到磁盘上的 Redo Log 文件)。此时 Redo Log 记录了所有修改,足以保证崩溃恢复。
在 Redo Log 中写入一个特殊的
PREPARE
记录,标记事务进入准备状态。Write Binlog:
Binlog: 是 MySQL Server 层的逻辑日志(Statement/Row/Mixed 格式),记录数据修改的逻辑操作(如 SQL 语句本身或行的前后镜像)。用于主从复制、数据恢复等。
将本次事务产生的 Binlog 写入磁盘上的 Binlog 文件。
Commit Phase:
在 Redo Log 中写入一个
COMMIT
记录。至此,事务才算正式提交成功。客户端收到确认。
关键点: 这个两阶段提交(先 Redo Prepare + Binlog 写盘,然后 Redo Commit)保证了 Binlog 和存储引擎数据之间的一致性。即使系统崩溃,重启恢复时也能根据 Redo Log 和 Binlog 的状态决定是提交还是回滚事务。
后台异步刷脏
事务提交后,被修改的脏页(Dirty Page)仍然在 Buffer Pool 内存中。
InnoDB 的后台线程(如 Page Cleaner Thread)会在合适的时机(根据检查点机制、Buffer Pool 空间压力等)将脏页异步地刷新(Flush)到磁盘上的数据文件(.ibd 文件)中。这通常发生在系统相对空闲时。
注意: 即使脏页还没刷盘,只要 Redo Log 已经持久化,数据库就能保证数据不丢失(崩溃恢复时 Redo Log 可以重做修改)。