MySQL中的redo和undo
文章不涉及复杂的知识点,只是谈谈对MySQL中的redo日志和undo日志的理解。方便深入理解MySQL内部原理
参考资料有:《MySQL存储引擎》《从根上理解MySQL》
redo log和undo log的作用
redo和undo都是保证事务的原子性。对于事务里的增删改操作要么全部执行成功,要么全部失败。
但是这两个日志实现原子性的维度不一样。要知道redo和undo是保证事务的原子性,所以它服务的主体是事务。先了解下事务可以分为这么几个阶段:
- 事务开始(
Begin语句或者start transaction) - 事务执行(可能是部分提交或者正在提交事务)
- 事务提交(
commit操作) - 事务回滚(
rollback操作) - 事务异常关闭(断电等因素)
那么
- redo日志保证的是事务提交后的原子性
- undo日志保证的是事务开始前的原子性
说白了就是不允许数据库中的数据不清不楚,比如A向B转账,需要经过以下两个操作
A的账户扣钱
B的账户加钱
无论是操作前还是操作后我们都只能接受两种结果:要么转账失败就当没发生过一样;要么转账成功B收到了钱。而redo保证的是后者,undo保证的是前者。
redo日志的实现(工作原理)
那么redo日志的工作原理是什么呢?先了解下事务提交都会发生什么。提交事务的大致流程可以这么理解。
- 先把数据所在的数据页加载到BufferPool中
- 修改BufferPool中的数据页中的数据(如果此时断电或者故障那就是undo日志的事儿了,后面再说);同时也会记录日志到
redo log buffer中 - 用户执行
commit语句 - 先把
redo log buffer中的数据刷新到磁盘 - 如果第五步执行完成,才会把BufferPool中的数据页刷新到磁盘(默认的)
MySQL的内存划分
也就是说,在操作数据页的同时也在redo log buffer中记录了一些信息,什么信息呢?比如插入了一条数据,那日志中就记录
- 分配的事务ID
- 每条redo日志生成一个序列号
LSN,该值是递增的。 - 插入的数据ID以及各个列的数据,
- 插入数据所在的表空间ID
- 插入数据所在页的偏移量等等
- 修改页中
槽的值,page directory信息等等,其他的不再列举,意思就是想表明,插入一条数据需要修改的地方可能会很多。
记录完毕之后,在提交事务时会先把redo log buffer刷到磁盘上。这样即使发生意外,MySQL重启时也能够恢复数据。
提问:既然数据页中都修改了数据,为什么还要在redo log中再存一份呢?
这是因为修改数据页是内存中的操作,而这些数据页所在磁盘位置不是连续的,而是随机IO,随机IO很慢,为了解决这个问题,专门划分出一块连续空间——就是`redo log buffer`来解决它。相对很快。省去了很多开销。典型的空间换时间。
checkpoint
假设我们的redo日志文件很大,有10GB,那MySQL重启的时候恢复数据需要扫描10GB的文件?这绝对不行!所以需要一种新的机制来保证MySQL重启时扫描最少的redo日志文件,它就是checkpoint技术
MySQL在重启时,已经持久化过的事务不需要再被扫描了,那么就需要一个变量来记录redo日志文件中已经持久化过的事务,暂且把它叫做checkpoint_lsn把。上面说过,每条redo日志都有自己的lsn序列号,并且是递增的。那么每当事务提交时,后台线程会不停的刷刷刷,把提交过的事务的最大lsn刷新到磁盘里。这样重启时就知道从哪里开始加载redo日志文件了。而checkpoint_lsn之前的就没什么用可以被覆盖,当redo日志文件(大小需要自己配置默认1GB)写满的时候就会从头写。
undo日志的实现(工作原理)
undo日志在修改数据页时先把原始数据保存一份,保存到回滚段中,当发生意外就把保存的这份原始数据取出来还原。每条记录都会存储事务ID和回滚指针ID,回滚会根据事务ID和回滚指针ID去undo日志中找到对应的版本还原数据
undo日志还可以应用于MVCC多版本控制,比如两个事务同时执行,当内存数据页已经被其中某个事务改变,而另一个数据要读它的时候,根据隔离级别可以读取它所需要的数据。如果默认隔离界别读取的就是undo形成的版本链的数据;如果是read uncommited读取的就是内存中是最新的数据。