第20章 说过的话一定要办到-redo日志(上)
这里就需要用到前面InnoDB存储结构的基础知识了
什么是redo日志?
InnoDB是以页为单位来存储数据的,我们平时进行的事务,也就是各种CRUD的操作都是针对访问页面进行的。
真正访问页面是在内存中进行的,于是访问之前需要将页从磁盘中加载到内存中才行。
既然操作是在内存中进行的,内存就有可能因为系统的崩溃导致数据丢失。如果这样的话,如何保持一致性呢?
最粗暴的做法是提交之前将所有修改的页面都同步到磁盘上,问题也很明显:
(1)浪费资源,存储引擎和磁盘交互是以页为单位进行的,如果只修改一个字节,也得刷新16kb,非常浪费
(2)随机IO速度比较缓慢,需要修改的多个页面不一定是在物理上连续存储的,导致很多随机I/O,非常慢
这种做法是以实际影响为导向的方法,如果我们换一种思路,以目标作为导向呢?
于是我们只需要记录哪些东西被修改了,记录修改的操作而不是直接保存那个操作本身造成的影响,这样不就更加灵活吗
这样即使系统崩溃了,只要之前的操作记录还在,就可以重新再执行恢复了,也就是直接满足持久性的要求了(状态不变,变了也得恢复)
然后再按照之前的记录重新操作,也就是重做日志,redo log。
这种方式叫做redo日志。和整个页面的修改刷新对比,这种只刷新日志到磁盘的方式更好:
(1)日志占用的空间很小
(2)redo日志是顺序写入磁盘的
Redo日志的格式
Redo日志只是一个记录而已,大部分的日志都有通用的格式:
Type:日志类型
space ID: 表空间ID
page number: 页号
data: 日志记录的具体内容
日志有简单日志类型和复杂日志类型,具体实现不用过多关注,了解知道有两种类型的日志即可
不管是哪种类型的日志,都会将执行过程中的修改操作记录下来,然后系统在崩溃以后可以根据日志还原修改
以组的方式写入redo日志
执行语句可能会修改很多页面,还会更新聚簇索引和二级索引中对应的页面。页面的修改都是发生在内存上的,于是许都需要进行redo日志记录
产生的日志被划分成很多不可以分割的组,也就是多个操作视为一个整体不可以分割
为什么要这样做呢?因为插入数据看起来只是一个操作,但是背后可能会涉及其他更多的附属操作,于是这些操作对整个数据库都有影响,因此需要将这些操作捆绑起来,视为一个整体记录在redo日志中
比如执行"悲观插入",也就是插入的时候可能需要页分裂,这个时候需要改动的地方可就多了,因为涉及到数据的转移和交换。牵一发而动全身:各种系统页面,段和区的统计信息等等都需要进行改动,总共需要很多条redo日志记录才能记录这一次的插入
也就是整个插入的过程是很多操作一起进行的,如果你只记录了一部分过程,那么如果后续需要恢复的实际就会恢复成不正确的状态。
因此执行原子性的操作的时候必须以组的形式存储日志,目的就是确保执行过程中的连带操作也被记录。
并且针对一个组的记录,在恢复的时候,要么所有的操作都恢复原样,要么就一条也不恢复,这就是一个整体思想的运用
怎么将这些操作划分成一个组的呢?通过标签实现的:
给每组最后一条日志后面加上一个特殊类型的redo日志,也就是MLOG-MULTI-REC-END类型,该类型只有一个type字段
所以为了保证原子性操作的一些列整体的操作,划分成组则必须有一个MLOG类型的结尾
这样系统从日志中恢复的时候,只有解析到有这个类型的日志的时候才认为是一组完整的redo日志操作,才会进行恢复,否则会放弃掉该组所有的日志
如果不是"悲观插入",那么就只生成一条日志,这样的话就不用MLOG类型来划分成组了,只需要指定这条记录type字段的比特位来表示是否是单一操作的日志记录
总结:使用成组的日志来保持一个操作的持久性,成组的日志加上特殊的尾巴作为成组的标志。如果只需单个日志那么直接标记日志的比特位即可
Mini-Transaction概念
从底层页面中进行一次原子访问的过程称之为Mini-Transaction。简称mtr
一次原子操作,那么说明可以是一组日志的操作,也可以是单个修改的日志操作。那么一个mtr可以包含一组redo日志
一个事务可以包含很多个语句,一个语句实际上就是很多mtr组成的,mtr又包含redo日志:

也就是一条语句的完成是多个原子性操作的实现,然后一个操作又可能导致很多修改,于是需要成组的redo日志来记录这一次的原子操作
个人思考:其实一次原子性的访问可以将多个redo操作视为一个整体进行操作,于是可以有多条redo日志记录这次目标操作
Redo日志的写入过程
通过mtr生成的日志都是保存在页中的,称之为block页
Redo日志缓冲区
写日志同样是先申请内存再刷新磁盘的,申请的内存就是redo缓冲区,该片缓冲区被分成连续若干个block
redo日志写入log buffer
从缓冲区中写入redo日志的过程是顺序的,先往前面的block中写,然后依次往后写。使用buf-free的全局变量来指定后续写入redo日志的在内存中的位置
总结:
Redo日志就是存储修改数据变动的记录。平时的事务操作涉及到各种数据的修改,这些都需要redo进行记录,以便不时之需来恢复到之前的状态。
这是为了保持事务的原子性和持久性所必须采取的一种折中的优化方案:否则时时刻刻都要从内存中刷新到磁盘了
redo日志方案的实现具体是通过记录修改的操作步骤,而不是修改造成的结果本身。