(二)日志系统:一条更新语句是如何执行的
(二)日志系统:一条更新语句是如何执行的
程序员朱永胜上篇文章我们介绍了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。
那么,一条语句的更新流程是什么样的?
MySQL 可以恢复到半个月内任意一秒的状态,是怎么做到的?
我们先复习下查询流程
这里我们需要注意的是,更新语句的流程和查询流程有两个区别,更新流程涉及两个重要的日志模块:
- redo log(重做日志)
- binlog(归档日志)
相信大家在这个面试,学习 MySQL 的过程中都反复听到这两个词
WAL 技术
在 MySQL 中,WAL(Write-Ahead Logging)技术是一种常用的持久化数据的机制,用于确保数据库的事务操作能够持久化到磁盘并保持数据的一致性。WAL 技术的核心思想是在事务进行修改之前,
先将修改操作记录到日志中,然后再将修改应用到数据库中。
具体来说,MySQL 中的 WAL 技术主要包括以下几个组件和步骤:
Redo Log(重做日志):Redo Log 是一种事务日志,用于记录数据库中发生的修改操作。在事务提交之前,MySQL 会将修改操作写入 Redo
Log,而不是直接写入磁盘。这样可以提高性能,因为磁盘写入是相对较慢的操作。Write-Ahead Logging(预写式日志):WAL 技术要求在事务提交之前,Redo
Log 必须先写入磁盘,然后再将修改操作应用到数据库中。这样即使在事务提交后发生系统崩溃,MySQL 也可以通过 Redo Log 来恢复数据。Redo Log Buffer(重做日志缓冲区):Redo Log Buffer 是一个内存缓冲区,用于暂存待写入 Redo Log 的修改操作。当事务提交时,Redo
Log Buffer 中的内容会被刷新到磁盘的 Redo Log 文件中。Checkpoint(检查点):Checkpoint 是一个标记点,表示在这个点之前的所有事务已经持久化到磁盘。MySQL 会定期将 Checkpoint 的位置更新到磁盘,以确保已经持久化的数据不会丢失。
Crash Recovery(崩溃恢复):当数据库发生崩溃或重启时,MySQL 会通过读取 Redo Log 来恢复数据的一致性。它会按照 Redo
Log 中的顺序,将每个事务的修改操作重新应用到数据库中,以还原数据的最新状态。
WAL 技术的优点是可以提高数据库的性能和可靠性。通过将修改操作先记录到 Redo
Log 中,可以避免频繁地写入磁盘,从而提高性能。同时,WAL 技术还可以确保数据的持久性和一致性,即使在系统崩溃或断电的情况下也能够恢复数据。
MySQL 中的 WAL 技术通过使用 Redo Log 和预写式日志的机制,确保事务的修改操作能够持久化到磁盘并保持数据的一致性。它是一种提高性能和可靠性的重要技术。
Redo Log 执行流程
当一个事务开始时,MySQL 会为该事务分配一个唯一的事务 ID,并将该事务的相关信息存储在内存中的事务控制块(Transaction Control
Block,TCB)中。在事务执行过程中,所有的修改操作都会被写入 redo log 缓冲区。这些修改操作包括插入、更新和删除等操作。
当事务提交时,MySQL 会将该事务的所有修改操作按照顺序写入 redo log 文件中。这些修改操作会被写入到 redo
log 缓冲区,然后通过后台线程定期将缓冲区中的内容刷新到磁盘上的 redo log 文件中。这个过程称为 redo log 的刷新。在事务提交之前,MySQL 会将 redo log 的刷新操作和数据页的刷新操作进行协调,以保证数据的一致性。这是通过使用 write-ahead
logging(预写式日志)的机制来实现的。即在事务提交之前,redo log 必须先写入磁盘,然后再将修改操作应用到数据库中。当数据库发生崩溃或重启时,MySQL 会在启动过程中读取 redo log 文件,并将其中的修改操作重新应用到数据库中,以恢复数据的一致性。这个过程称为崩溃恢复。
Write Pos 和 CheckPoint
在 MySQL 的 redo log 中,有两个重要的概念:write pos(写入位置)和 checkpoint(检查点)。
Write Pos(写入位置):Write Pos 是指当前事务写入 redo log 的位置。当一个事务提交时,其修改操作会被写入 redo log 中的某个位置,Write
Pos 指向这个位置。下一个事务的修改操作将会从 Write Pos 指向的位置开始写入。Checkpoint(检查点):Checkpoint 是指一个标记点,表示在这个点之前的所有事务已经持久化到磁盘。当一个事务提交时,它的修改操作会被写入 redo
log,并且会更新 Checkpoint 的位置。这样,在 Checkpoint 之前的 redo log 中的操作可以被认为是已经持久化到磁盘的。
Checkpoint 的作用是用于数据库的恢复和崩溃恢复。当数据库发生崩溃或重启时,MySQL 会从 Checkpoint 的位置开始,读取 redo
log 中的操作,并将其应用到数据库中,以还原数据的一致性。
Write Pos 和 Checkpoint 之间的关系是,Write Pos 会不断向前移动,指向最新的写入位置,而 Checkpoint 会根据一定的策略进行更新,以标记已经持久化到磁盘的操作。
需要注意的是,Write Pos 和 Checkpoint 的位置是相对于 redo log 文件的偏移量,而不是绝对的字节位置。它们的值通常以字节为单位,表示相对于 redo
log 文件起始位置的偏移量。
Write Pos 表示当前事务写入 redo log 的位置,Checkpoint 表示已经持久化到磁盘的操作的位置。Write
Pos 会不断向前移动,而 Checkpoint 会根据一定的策略进行更新,用于数据库的恢复和崩溃恢复。
Redo Log 是固定大小的,超出会发生什么
当 redo log 的固定大小不足以容纳新的修改操作时,MySQL 会触发一个称为 “redo log 空间不足 “
的错误。在这种情况下,MySQL 会停止新的事务提交,直到有足够的空间来写入 redo log。
为了解决 redo log 空间不足的问题,可以采取以下几种方法:
增加 redo log 的大小:可以通过修改 MySQL 的配置参数
innodb_log_file_size
来增加每个 redo log 文件的大小。增加 redo
log 的大小可以提供更多的空间来存储修改操作,从而延长 redo log 的使用寿命。增加 redo log 文件的数量:可以通过修改 MySQL 的配置参数
innodb_log_files_in_group
来增加 redo log 文件组中的文件数量。增加文件数量可以增加 redo
log 的总大小,从而提供更多的空间来存储修改操作。提交事务并清空 redo log:如果当前的事务已经提交,但 redo log 空间不足,可以尝试手动提交其他未提交的事务,以释放 redo
log 空间。这可以通过执行COMMIT
语句来提交事务。优化事务的写入操作:可以通过优化事务的写入操作,减少对 redo log 的写入量。例如,可以合并多个小事务为一个大事务,减少 redo
log 的写入次数。
需要注意的是,增加 redo log 的大小或数量可能会增加系统的负载和崩溃恢复的时间。因此,在调整 redo
log 大小时,需要综合考虑系统的性能和可靠性需求,并进行充分的测试和验证。
什么是 Binlog 日志
Binlog(二进制日志)是 MySQL 的服务器层产生的一种日志,用于记录数据库中的所有修改操作,包括数据定义语言(DDL)和数据操作语言(DML)等操作。
Binlog 以二进制格式记录了对数据库的逻辑修改操作,而不是直接记录对数据页的具体修改。它包含了一系列的事件(Event),每个事件都代表了一个数据库操作,如插入、更新、删除等。
Binlog 的主要作用是用于 数据复制和恢复
。通过将 Binlog 传递给其他 MySQL 实例,可以实现数据的复制和同步。其他 MySQL 实例可以读取 Binlog 中的事件,并将其中的修改操作应用到自己的数据库中,从而实现数据的复制和同步。
此外,Binlog 也可以用于数据恢复。在误操作、数据丢失或灾难恢复的情况下,可以通过读取 Binlog 来还原数据。通过逐个回放 Binlog 中的事件,可以将数据库恢复到特定的时间点或特定的操作之前的状态。
Binlog 是追加写入的,不会被重复使用,以保留完整的修改历史。它可以通过配置参数进行启用和配置,包括指定 Binlog 的存储位置、设置 Binlog 的大小和保留时间等。
为什么 MySQL 会有两个日志,redo Log 和 binlog?
MySQL 之所以同时使用 redo log 和 binlog 两个日志,是因为它们具有不同的功能和用途。
Redo Log(重做日志):
- 功能:Redo log 是 InnoDB 存储引擎特有的日志,用于保证事务的持久性和一致性。它记录了数据库中发生的修改操作,包括插入、更新和删除等操作。
- 作用:在数据库崩溃或重启时,通过读取 redo log 来恢复数据的一致性。它可以将未持久化到磁盘的修改操作重新应用到数据库中,以还原数据的最新状态。
- 特点:redo log 是 物理日志,记录了对数据页的具体修改操作。它是循环写入的,可以重复使用,以减少磁盘 IO 的开销。
Binlog(二进制日志):
- 功能:Binlog 是 MySQL 的服务器层产生的日志,记录了数据库中的所有修改操作,包括数据定义语言(DDL)和数据操作语言(DML)等操作。
作用:Binlog 主要用于数据复制和恢复。它可以被其他 MySQL 实例读取,并将其中的修改操作应用到自己的数据库中,实现数据的复制和同步。同时,Binlog 也可以用于数据恢复,例如在误操作或数据丢失时,可以通过读取 Binlog 来还原数据。 - 特点:Binlog 是 逻辑日志,记录了对数据的逻辑修改操作。它是追加写入的,不会被重复使用,以保留完整的修改历史。
- 功能:Binlog 是 MySQL 的服务器层产生的日志,记录了数据库中的所有修改操作,包括数据定义语言(DDL)和数据操作语言(DML)等操作。
redo log 保证了事务的持久性和一致性,而 binlog 则提供了数据复制和恢复的功能。它们共同工作,确保了 MySQL 数据库的数据安全和可靠性。
举一个例子
1 | mysql> update T set c=c+1 where ID=2; |
- 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2
这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。 - 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare
状态。然后告知执行器执行完成了,随时可以提交事务。 - 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
最后三步看上去有点 “ 绕 “,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是 “ 两阶段提交 “。
MySQL 中的两阶段提交
在 MySQL 中,redo log 和 binlog 是两个不同的日志文件,它们都用于确保数据的一致性和持久性。它们的写入顺序和提交顺序有所不同。
Redo Log(重做日志):
- Redo log 是 MySQL 用于崩溃恢复的机制,它记录了事务对数据库所做的修改操作。
- 当事务执行时,MySQL 首先将修改操作记录到 redo log 中,然后将其写入磁盘。
- 这样做的目的是为了在系统崩溃时,能够通过 redo log 来恢复未完成的事务,保证数据的一致性。
Binlog(二进制日志):
- Binlog 是 MySQL 用于数据复制和恢复的机制,它记录了数据库的修改操作。
- 当事务提交时,MySQL 将修改操作记录到 binlog 中,但不立即写入磁盘。
- Binlog 的写入是异步的,可能会有一定的延迟。
现在来解释为什么 MySQL 先写 redo log,然后等 binlog 写完后才提交:
事务的持久性和恢复能力:
- 通过将修改操作记录到 redo log 中,MySQL 可以确保即使系统崩溃,也能够通过 redo log 来恢复未完成的事务,保证数据的一致性。
- 因此,redo log 的写入是在事务执行期间进行的,以提供更好的性能。
数据复制和恢复:
- Binlog 用于数据复制和恢复,它记录了所有的数据库修改操作。
- 在事务提交之后,MySQL 将修改操作记录到 binlog 中,以供主从复制等场景使用。
- 为了保证数据的一致性,MySQL 会等待 binlog 的写入完成,然后才提交事务。
所以,MySQL 先写 redo log,然后等 binlog 写完后才提交的目的是为了 保证数据的一致性和持久性 , 并提供数据复制和恢复的能力
。这样的设计可以提高性能,并确保在系统崩溃或数据复制场景下的数据完整性。希望这次解释更加清晰明了。如果还有任何疑问,请随时提问。
没写完发生 Crash 了会出现什么情况?
仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了
crash,会出现什么情况呢?
- 先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo
log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。但是由于 binlog 没写完就 crash 了,这时候
binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后你会发现,如果需要用这个
binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是
0,与原库的值不同。 - 先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c
的值是 0。但是 binlog 里面已经记录了 “ 把 c 从 0 改成 1” 这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行
c 的值就是 1,与原库的值不同。
可以看到,如果不使用 “ 两阶段提交 “,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。