0、前言
本文将详细介绍MySQL数据库中的REDO日志的作用、实现了什么功能、具体机制和优缺点。需要具有一定的数据库基础、熟悉事务的ACID的前提下可以更好地阅读😄
对于事务,我们知道有原子性、一致性、隔离性和持久性四种特性。
简单概括就是:
- 原子性:事务是最小的执行单位,不可再拆分了
- 一致性:对于事务的操作,要保证结果的一致。比如一边+了50,另一边减少50,则两边必须都完成or都没完成才可以,不能够只完成一部分。
- 隔离性:针对多线程并发情况,要保证一个事务在线程执行的过程中,不被别的线程所干扰、改动数据。
- 持久性:就是事务提交了 ,做完了,那这个数据就要持久地保存在磁盘里。不能说提交了结果后面发现数据没在里面,那就乱套了。
事务的隔离性由 锁机制 实现。
而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。
REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
1、基础知识——缓冲池Buffer Pool与"刷盘"操作
InnoDB存储引擎操作数据库的时候 ,并不是直接通过sql语句对磁盘进行crud,因为直接操作磁盘会带来大量的 IO,以及效率和速度底下等问题。所以实际上,InnoDB是先将磁盘的数据加载到内存中,然后对内存进行crud以后,再通过checkpoint机制(按照一定的时间间隔进行刷盘)把做好的数据传回磁盘,也就是所谓“刷盘”。
对于这样的操作,虽然速度快了,但可靠性就会出问题。
因为对于内存而言,并不能保证数据的持久化,如果发生断电、宕机的问题,内存中的数据就全都消失了!这样的结果必然是不可以容许的。
例如,对于一个事务,已经做完并提交了,内存中的数据也操作完了,本来此时应该刷盘回磁盘,从而保证持久化,结果这时候数据库宕机了,那么这段数据就丢失了、无法恢复。
那么该如何解决这种持久性的问题呢?使用REDO日志!
2、REDO日志
2.1 REDO日志的组成
REDO日志其实包含2部分:
(1)重做日志的缓冲 (redo log buffer),保存在内存中,容易丢失。其参数设置:innodb_log_buffer_size默认16M大小。
(2)重做日志的文件 (redo log file) ,保存在硬盘中,是持久的。
2.2 REDO日志的基本实现流程
InnodeDB的更新操作采用Write Ahead Log的策略,关键就在于:先写入日志,再写入磁盘,即下图的顺序1、2、3、4。其具体解析如下。
第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝。
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值。
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式。
第4步:定期将内存中修改的数据刷新到磁盘。
解读
- 对于前2步,需要注意:update事务每做1条,就要在redo log buffer中更改一条!而不是commit全部事务以后才更新redo log buffer。
这样做的好处是,如果没有commit事务就发生宕机,那就全部数据丢失,但是无所谓!因为反正没commit的事务也不需要保存,丢失就对了!恰恰保证了事物的一致性。 - 对于第3步,需要注意:事务commit后,就要把内存中的redo内容刷入磁盘中的redo file,后期才方便恢复数据
所以,REDO日志的核心思想就是:如果因为数据库宕机等问题导致内存丢失,但是具体操作已经存入redo file了,下次直接从磁盘的redo file恢复这次事务的操作即可。
2.3 redo log的刷盘策略(即上一小节的第3步)
redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以一定的频率刷入到真正的redo log file 中。这里的一定频率怎么看待呢?这就是我们要说的刷盘策略。
注意,redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存 (page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。
那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
- 设置为0 :表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)
- 设置为1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值 )
- 设置为2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。(这种策略,就算数据库宕机了,但只要操作系统不挂掉,那么数据仍然会从page cache刷盘进去。)
3、基础知识:事务的回滚
事务需要保证 原子性 ,也就是事务中的操作要么全部完成,要么什么也不做。
但有时候事务执行到一半会出现一些情况,比如:
情况一:事务执行过程中可能遇到各种错误,比如 服务器本身的错误 , 操作系统错误 ,甚至是突然断电导致的错误。
情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。
以上情况出现,我们需要把数据改回原先的样子,这个过程称之为 回滚
4、UNDO日志
相对于REDO日志文件记录的是update以后的数据,UNDO日志是用于记录update之前的数据(因为UNDO日志的作用就是恢复update之前的数据嘛,即回滚到之前的状态)。
4.1 作用
4.2 存储结构
InnoDB对undo log的管理采用段的方式,也就是 回滚段(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,而在每个undo log segment段中进行 undo页 的申请。
4.3 流程
(1)当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会先被复制到回滚段。
(2.1)如果在commit之前执行回滚操作,MySQL会使用UNDO日志来撤销事务所做的所有update操作:
回滚过程中使用的Undo日志记录了对数据进行修改或删除操作的信息,包括修改前的值和修改后的值。当执行回滚操作时,InnoDB会通过Undo日志将数据恢复到回滚之前的状态。
具体来说,Undo日志实现回滚的原理如下:
在开始一个事务之前,InnoDB会为该事务分配一块Undo日志区域,并记录该区域的起始位置。
当事务对一个数据进行修改时,InnoDB会将修改前的版本写入Undo日志中。
当回滚操作发生时,InnoDB会遍历Undo日志中的记录,反向撤销所有对数据的修改操作,即将数据恢复到修改前的状态。
回滚操作完成后,InnoDB会将Undo日志标记为已使用,并在下次清理Undo日志时将其释放。
(2.2)如果在commit后执行回滚操作:
当一个事务提交(commit)之后,MySQL会将该事务对应的Undo日志标记为“已提交”,但不会立即删除它。相反,它将继续存储在磁盘上,直到InnoDB的回收线程将其清理。
这是因为Undo日志被用于实现MVCC(多版本并发控制)机制,以支持读已提交(read committed)和可重复读(repeatable read)隔离级别的事务。
在可重复读隔离级别下,当一个事务提交后,它对应的Undo日志就像是一个时间点的快照一样,记录了此时事务对数据的修改前的状态。如果需要回滚该事务,MySQL就会使用这些Undo日志来将数据还原到提交之前的状态。
5、总结
undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。
redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程。