update table set is_deleted = 1 where id = 1;
同查询语句一样更新语句一样会经过连接器、查询缓存、分析器、优化器、执行器这几个流程。
在查询缓存这边,查询语句会创建缓存,那么更新语句会清空缓存,且是清空该表上的所有缓存。
分析器分析词法语法、优化器决定语句的执行方案、执行器则具体执行数据的更新。
与查询流程不一样的是,更新还涉及到两个日志模块,redo log(重做日志)和 binlog(归档日志)。
redo log
redo log 可以类比成记账,如果数据库的每次更新操作都要去磁盘写一次数据无疑是性能低下的,那么就可采用类似记账的模式,先把要更新的操作记录下来并更新内存,待服务器空闲时,或账本写满了,再整理数据写入磁盘。这个就是 MySQL 中的 WAL 技术,全称是 Write-Ahead Logging。这个账本就是 redo log。
InnoDB 的 redo log 是固定大小且循环的,那么必然会有 redo log 写满的情况,此时服务器只能停止新数据的写入,把老数据整理后写入磁盘,擦除 redo log 中对应的位置。大致流程是:write pos 是当前记录的位置,checkpoint 是最末尾的数据位置,他们中间是空的可记录的空间。随着数据的不断写入,write pos 会逐渐逼近 checkpoint 所在的位置,当 checkpoint 被追上的时候,就是 redo log 被写满了。此时服务器要暂停写入,整理最末尾的数据,写入磁盘后擦除数据,使 checkpoint 远离 write pos 。
write pos checkpoint
┌—————|————-|—————┐
└————————————————┘指向头部
redo log 可以保证 InnoDB 在数据库异常时保证提交记录不会丢失(持久性),这个能力被称为 crash-safe。
binlog
redo log 是 InnoDB 引擎的日志,而数据库服务本身自己也有日志,称为 binlog(归档日志)。在最开始 MySQL 自带的引擎是 MyISAM 但是它不能保证数据的持久性,binlog 仅作为归档日志。InnoDB 作为第三方引擎,则自行实现了独立的日志系统以实现数据的持久性。
redo log 与 binlog 不同点
- redo log 是 InnoDB 引擎独有;binlog 是 MySQL 的 Server 层实现的,所有引擎都可使用。
- redo log 是物理日志,记录了某个数据页做了什么修改;binlog 是逻辑日志,记录具体的sql语句。
- redo log 是循环写,空间固定有限;binlog 是追加写,历史日志不会被覆盖。
一条更新语句的执行过程
update table set is_deleted = 1 where id = 1;
- 执行器调用引擎查询 id=1 的数据,有索引就走索引。如果数据在内存中存在,则直接返回。不存在则需要从磁盘读到内存再返回。
- 执行器拿到数据后,修改 is_deleted=1,再调用引擎写入这行数据。
- 引擎将这行数据更新到内存中,同时将更新操作记录到 redo log,此时 redo log 处于 prepare 状态。然后告知执行器执行完成,随时可提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎提交事务,引擎把刚刚的 redo log 改成 commit 状态,更新完成。

为什么 redo log 要设计成两段提交?
由于两套日志系统相对独立,不使用两段提交会发生如下可能:
- 先写 redo log 后写 binlog。在 redo log 写完,binlog 还未写完时,MySQL 奔溃重启。重启后 MySQL 可以通过 redo log 恢复提交后数据。但是 binlog 缺少对应日志,如果未来依靠此 binlog 恢复数据,会导致已提交的数据丢失。
- 先写 binlog 后写 redo log。在 binlog 写完,redo log 还未写完时,MySQL 奔溃重启。重启后 MySQL 通过 redo log 恢复提交前的数据,但是 binlog 已包含更新日志,而如果未来依靠此binlog恢复数据,会导致未提交的数据出现。
- 两段提交可以在系统恢复后检查两个日志的数据,同时存在则正常,redo log 存在但 binlog 不存在则事务回滚。
其他
innodb_flush_log_at_trx_commit参数设置成1可以把每次事务的 redo log 都持久化到磁盘。sync_binlog参数设置成1可以把每次事务的 binlog 持久化到磁盘。- 两段提交是保证一致性的常用方案。
