InnoDB引擎下的redolog与undolog
MySQL日志
- undo log(回滚日志):由InnoDB生成的日志,实现事务中的原子性,主要用于事务回滚和MVCC。
- redo log(重做日志):由InnoDB生成的日志,实现事务中的持久性,主要用于掉电等故障恢复。
- bin log(归档日志):由Server层生成的日志,主要用于数据备份和主从复制。
undo log
undo log是一种用于撤销回退的日志。在事务提交之前,MySQL会先记录更新前的数据到undo log日志文件里面,当事务回滚时,可以利用undo log回滚。
每当InnoDB引擎对一条记录操作时,要把回滚时的信息记录到undo log日志里:
- 插入:会把这条记录的主键记录下来,回滚时根据主键去删除即可。
- 删除:将该条记录的内容全部记录下来,这样回滚时把这些内容组成的记录插入到表中。
- 更新:把被更新的列和旧值记录下来,回滚时将这些列更新为旧值。
在发生回滚时,就读取undo log里的数据,然后做原先相反操作:delete回滚则进行insert等等。
一条记录的每次更新操作产生的undo log格式都有一个roll_pointer指针和一个trx_id事务id。
- 通过trx_id知道该记录被哪个事务修改了
- 通过roll_pointer指针可以将这些undo log串成链表,这个链表就是版本链。
作用
- 实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执 行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
- 实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。
Buffer Pool
是InnoDB引擎设计的缓冲池,用以提高读写性能。
- 当读取数据时,如果数据在Buffer Pool中,那么客户端会直接读取Buffer Pool中的数据,不在则去磁盘中读,然后缓存到Buffer Pool。
- 当修改数据时,如果数据在缓冲池中,那么直接修改缓冲池中的页,然后将该页标记为脏页,然后又后台线程异步将脏页刷到磁盘。
在 MySQL 启动的时候,InnoDB 会为 Buffer Pool 申请一片连续的内存空间,然后按照默认的16KB
的大小划分出一个个的页, Buffer Pool 中的页就叫做缓存页。此时这些缓存页都是空闲的,之后随着程序的运行,才会有磁盘上的页被缓存到 Buffer Pool 中。
在缓冲池中,除了缓存有 索引页 和 数据页 ,还有 Undo 页,插入缓存,自适应哈希索引,锁信息等。
由于InnoDB引擎操作内存是以16KB,即一个页为单位进行操作的,所以即使是操作一条数据,也是将该数据所在的页全部加载到缓存池中,然后通过页的页目录去定位到该记录。
Undo 页
在开启事务后,InnoDB每次更新数据前会记录相应的 undo log操作,如果是更新操作,会生成undo log版本链,版本链就写在Undo 页中。
redo log
为了防止断电导致数据丢失,当有一条记录需要更新时,InnoDB引擎会先更新内存,同时标记该页为脏页,然后将本次对该页的修改以 redo log形式记录下来,这就算更新完成了。然后InnoDB引擎会在适当的时候,由后台线程将缓存池中的脏页刷到磁盘中。
WAL技术(Write-Ahead Logging)
WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
redo log
物理日志,记录了某个数据页做了什么修改,每当执行一个事务就会产生这样的一条或者多条物理日志。
在事务提交时,只要先将redo log持久化到磁盘即可,可以不需要等到将缓存在缓存池里的脏页持久化。
当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。
修改undo页面也会记录redo log
开启事务后,InnoDB 层更新记录前,首先要记录相应的 undo log,如果是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面。
不过,在内存修改该 Undo 页面后,需要记录对应的 redo log。
redo log和undo log的区别
- redo log记录了此次事务完成后的数据状态,记录的是更新后的值
- undo log 记录了此次事务开始前的数据状态,记录的是更新前的值
事务提交前崩溃,重启后会通过undo log 回滚事务,事务提交后发生了崩溃,重启后会通过redo log 恢复事务。
有了 redo log,再通过 WAL 技术,InnoDB 就可以保证即使数据库发生异常重启,之前已提交的记录都不会丢失,这个能力称为 crash-safe(崩溃恢复)。可以看出来, redo log 保证了事务四大特性中的持久性。
为什么要有 redo log
为什么需要 redo log 这个问题有两个答案:
- 实现事务的持久性,让 MySQL 有 crash-safe 的能力,能够保证 MySQL 在任何时间段突然崩溃,重启后之前已提交的记录都不会丢失;
- 将写操作从「随机写」变成了「顺序写」,提升 MySQL 写入磁盘的性能。
redo log buffer
产生的redo log也不是直接写入磁盘,这是为了避免产生大量磁盘IO。因此redo log也具有自己的缓存,每当产生一条redo log时,会先写入到redo log buffer,后续持久化到磁盘中。
刷盘时机:
-
MySQL正常关闭时。
-
当redo log buffer中记录的写入量大于redo log buffer内存空间的一半时,会触发落盘。
-
InnoDB后台线程每隔一秒都会将redo log buffer持久化到磁盘。
-
每次事务提交时都将缓存在redo log buffer里的redo log直接持久化到磁盘(该策略由innodb_flush_log_at_trx_commit控制)
innodb_flush_log_at_trx_commit参数
参数可取的值有 0、1、2
- 0:表示每次事务提交时,还是将redo log留在redo log buffer中,该模式下事务提交时不会主动触发写入磁盘操作。
- 1:每次事务提交时,都将缓存在redo log buffer的redo log 刷到磁盘中。
- 2:每次事务提交时,都只是将缓存在redo log buffer写道redo log文件中,而不是立即写入磁盘。
InnoDB的后台线程会每隔一秒进行持久化操作:
- 对于参数0:会把缓存在redo log buffer中的redo log通过write写到操作系统的Page Cache中,然后调用fsync持久化到磁盘。
- 对于参数2:调用fsync将缓存在Page Cache里的redo log 持久化到磁盘。
使用时机
- 对数据安全性要求高的场景下,把该参数设置为1
- 在一些可以容忍数据库崩溃1s丢失数据的场景,可以将该值设置为0,这样可以明显减少日志同步到磁盘的IO操作
- 安全性和性能折中的方案是2,虽然没有0的性能高,但是安全性更好,只要操作系统不宕机,数据库崩溃了也不会丢失数据,性能也比1要高。
redo log文件满后处理
redo log在InnoDB引擎的重做日志文件组中,存在两份。ib_logfile0, ib_logfile1
重做日志文件组采用循环写的方式,当0写完则写1,1写完再回头写0。用checkpoint标记擦除开始的地方,writepos标记可以写的地方。
- write pos和checkpoint的移动是顺时针方向。
- write pos~checkpoint 之间的部分 用于记录新的更新操作。
- checkpoint ~ write pos之间的部分是待刷盘的脏数据页。
当write pos追上checkpoint时意味着redolog满了,此时MySQL不能执行新的更新操作,将被阻塞,停下来将Buffer Pool的脏页刷新到磁盘中,然后标记redo log哪些记录可以擦除,接着对旧的redolog记录进行擦除,腾出空间后checkpoint会继续顺时针移动,继续执行新的更新操作。
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术