redo日志和undo日志的理解

150 阅读4分钟

MySQL中的redo和undo

文章不涉及复杂的知识点,只是谈谈对MySQL中的redo日志和undo日志的理解。方便深入理解MySQL内部原理
参考资料有:《MySQL存储引擎》《从根上理解MySQL》

redo logundo log的作用

redo和undo都是保证事务的原子性。对于事务里的增删改操作要么全部执行成功,要么全部失败。

但是这两个日志实现原子性的维度不一样。要知道redo和undo是保证事务的原子性,所以它服务的主体是事务。先了解下事务可以分为这么几个阶段:

  1. 事务开始(Begin语句或者start transaction
  2. 事务执行(可能是部分提交或者正在提交事务)
  3. 事务提交(commit操作)
  4. 事务回滚(rollback操作)
  5. 事务异常关闭(断电等因素)

那么

  • redo日志保证的是事务提交后的原子性
  • undo日志保证的是事务开始前的原子性

说白了就是不允许数据库中的数据不清不楚,比如A向B转账,需要经过以下两个操作

A的账户扣钱
B的账户加钱

无论是操作前还是操作后我们都只能接受两种结果:要么转账失败就当没发生过一样;要么转账成功B收到了钱。而redo保证的是后者,undo保证的是前者。

redo日志的实现(工作原理)

那么redo日志的工作原理是什么呢?先了解下事务提交都会发生什么。提交事务的大致流程可以这么理解。

  1. 先把数据所在的数据页加载到BufferPool中
  2. 修改BufferPool中的数据页中的数据(如果此时断电或者故障那就是undo日志的事儿了,后面再说);同时也会记录日志到redo log buffer
  3. 用户执行commit语句
  4. 先把redo log buffer中的数据刷新到磁盘
  5. 如果第五步执行完成,才会把BufferPool中的数据页刷新到磁盘(默认的)

MySQL的内存划分

image.png

也就是说,在操作数据页的同时也在redo log buffer中记录了一些信息,什么信息呢?比如插入了一条数据,那日志中就记录

  1. 分配的事务ID
  2. 每条redo日志生成一个序列号LSN,该值是递增的。
  3. 插入的数据ID以及各个列的数据,
  4. 插入数据所在的表空间ID
  5. 插入数据所在页的偏移量等等
  6. 修改页中的值,page directory信息等等,其他的不再列举,意思就是想表明,插入一条数据需要修改的地方可能会很多。

记录完毕之后,在提交事务时会先把redo log buffer刷到磁盘上。这样即使发生意外,MySQL重启时也能够恢复数据。

提问:既然数据页中都修改了数据,为什么还要在redo log中再存一份呢?

这是因为修改数据页是内存中的操作,而这些数据页所在磁盘位置不是连续的,而是随机IO,随机IO很慢,为了解决这个问题,专门划分出一块连续空间——就是`redo log buffer`来解决它。相对很快。省去了很多开销。典型的空间换时间。

checkpoint

假设我们的redo日志文件很大,有10GB,那MySQL重启的时候恢复数据需要扫描10GB的文件?这绝对不行!所以需要一种新的机制来保证MySQL重启时扫描最少的redo日志文件,它就是checkpoint技术

redo_undo.drawio.svg

MySQL在重启时,已经持久化过的事务不需要再被扫描了,那么就需要一个变量来记录redo日志文件中已经持久化过的事务,暂且把它叫做checkpoint_lsn把。上面说过,每条redo日志都有自己的lsn序列号,并且是递增的。那么每当事务提交时,后台线程会不停的刷刷刷,把提交过的事务的最大lsn刷新到磁盘里。这样重启时就知道从哪里开始加载redo日志文件了。而checkpoint_lsn之前的就没什么用可以被覆盖,当redo日志文件(大小需要自己配置默认1GB)写满的时候就会从头写。

undo日志的实现(工作原理)

undo日志在修改数据页时先把原始数据保存一份,保存到回滚段中,当发生意外就把保存的这份原始数据取出来还原。每条记录都会存储事务ID和回滚指针ID,回滚会根据事务ID和回滚指针ID去undo日志中找到对应的版本还原数据

undo日志还可以应用于MVCC多版本控制,比如两个事务同时执行,当内存数据页已经被其中某个事务改变,而另一个数据要读它的时候,根据隔离级别可以读取它所需要的数据。如果默认隔离界别读取的就是undo形成的版本链的数据;如果是read uncommited读取的就是内存中是最新的数据。