目录
0 引言
针对mysql日志系统的一些总结,主要是Innodb的redo日志和undo日志,然后就是server层的binlog日志,复习资料主要来自极客时间《Mysql实战45讲》和《MySQL是怎样运行的》
1 redo日志
redo日志是什么
redo日志记录了我们对于数据的改动操作。我们在对数据库进行操作的时,都是对buffer pool中的页面进行操作,当系统崩溃时这些页面也就会丢失从而使得数据库的数据出现丢失。所以我们需要将对数据库的改动操作记录到磁盘中,直接将buffer pool中的页面刷新到磁盘中是十分浪费时间和空间的,我们可以将对每次对数据库的改动记录成一条redo日志,这样只刷新较小的redo日志到磁盘中就更加节约资源。
Innodb引擎特有的
redo日志的写入过程
首先说明一点,就是我们的redo日志是按组写入的,也就是所谓的MTR,MTR表示对底层页面的一次原子访问,一次原子访问可能产生多条redo日志,这些redo日志就称为一个组。
我们的redo日志也是先写到缓存中,再写到磁盘上的。在内存中有一个redo log buffer的区用来存储我们的redo日志,这部分区域分为了一个个block,每个block占用了512字节。这些block占用的是一块连续的空间,我们向里面写redo日志就是按照组从第一个block开始顺序写入的
向磁盘中写redo日志的时机
-
log buffer不足时
-
事务提交时
-
将脏页flush会磁盘前需要先将redo日志刷新到磁盘
-
后台有一个线程以大约每秒一次的频率刷新日志
-
正常关闭服务器时
-
做checkpoint时
几个关键的变量
-
buf_free 记录了log buffer中下一条要写入的redo日志的位置
-
lsn 记录了当前写入log buffer的redo日志数量
-
buf_next_to_write 标记当前log buffer中已经有哪些redo日志被刷新回磁盘了
-
flushed_to_disk_lsn 表示刷新到磁盘中的日志的全局变量
-
checkpoint_lsn 记录已经没有用的redo日志的量
我们的redo日志最终会被循环写到磁盘中的一些redo日志文件中,这个日志文件是有固定大小的,为了防止追尾导致redo日志被覆盖的问题引入checkpint_lsn这个值记录了当前可以被覆盖的redo日志的最大lsn值。
某一个时刻,这几个属性的值的可能关系如下:
lsn = buf_free >= flush_to_disk_lsn = buf_next_to_write >= checkpoint_lsn
checkpoint_lsn的确定方法
要说checkpoint_lsn的确定方法,先来看我们flush链表中的两个属性,flush链表是buffer pool中的一个链表,维护了我们做了修改的页面,每个页面中有两个属性,一个是oldest_modification:该属性记录了第一次对这个页面进行修改的事务的lsn值,一个是newest_modification:该属性记录了最近修改这个页面的事务的lsn值。当我们对页面进行修改后就会将页面放入到flush链表的头部,若之后再对这个页面进行修改,不需要移动页面在链表中的位置,只需要修改其newest_modification的值就行了。
checkpoint_lsn记录了可以覆盖的redolog量,当我们redo日志对应的页面已经被刷新会磁盘中,那么这个页面对应的redo日志也就可以被覆盖了,checkpoint_lsn的值在进行checkpoint的时候进行更新,我们只需要获取系统中最早修改脏页的oldest_modification,那么lsn小于这个值的页面肯定就已经被刷新回了磁盘中,所以这个oldest_modification值就是checkpoint_lsn值。
崩溃恢复
根据checkpoint找到redo日志文件的恢复起点后就可以挨着读取redo日志进行数据恢复了
2 undo日志
undo日志是什么
undo日志是为了保证我们事务的原子性而引入的,当我们开启一个事务时,我们可以通过回滚操作来将数据库恢复到原来某个时间点的状态,这就需要我们的undo日志;同时,当我们事务还未提交系统崩溃了,那么我们在该事务中进行的修改就不应当失效,这也需要用到undo日志的。undo日志记录了我们对页面中的记录进行了怎样的修改。
undo日志的存储方式
undo日志是直接存放在undo页面中的,undo日志又可分为两个大类,分别是insert undo日志和update undo日志,然后根据表的类型不同又可以进一步划分,分别是普通表的undo日志(insert 和 update)和临时表的undo日志(insert 和update),这些undo 日志页面是通过链表组织起来的,所以一个事务的undo日志被存放在以下四个链表中
-
普通表的insert日志链表
-
普通表的update日志链表
-
临时表的insert日志链表
-
临时表的insert日志链表
这些链表的头结点又全部被保存在回滚段的slot槽中(回滚段的一个slot槽指向一个undo日志链表)
需要注意的是,并不是一开始事务就给事务分配了这些undo链表,只有在需要记录相应的undo日志时才需要创建对应的链表
undo日志与记录之间的关系
聚簇索引中的记录有三个隐藏列,分别是row_id,trx_id,roll_pointer,其中的row_pointer就指向了该条记录最新的一条undo日志,undo日志中也有一个roll_pointer属性,指向该undo日志之前的一条undo日志,通过roll_pointer属性也就构成了一条记录的版本链,服务于MVCC。
3 binlog日志
binlog日志是什么
binlog是server层的日志,称为归档日志,只用作归档用,和redo日志的区别
-
redo日志是Innodb引擎特有的,binlog是server层提供的
-
redo日志是物理日志,记录了我在哪个数据页上做了什么修改,binlog是逻辑日志,记录的是执行语句的原始逻辑
-
redo日志是循环写,binlog日志是追加写,所以binlog不存在覆盖问题
binlog日志的分类
mysql的高可用架构都是基于binlog来实现的,我们的binog是归档所用,主要两种格式,分别是statements和rows格式,两个格式的区别如下:
-
statements
改格式下的binlog记录的是我们直接执行的语句,比如执行
delete * from t where id = 1;
这是产生的binlog记录的就是这条执行的语句。但是这类binlog存在一个问题就是我们的语句在主库和从库中有可能会通过不同的索引执行,这样就可能会导致中裤和从库的不一致性
-
rows
改格式下的binlog记录的是我们对哪个表的哪一行做了什么样的修改,比如上面的那条delete语句,在生成rows格式的binlog时就是记录了删除表t的id=1的行。这种格式的binlog记录的信息更加完整。
4 redo日志的两阶段提交
来看一下我们一条更新语句的执行过程,首先在内存中进行数据页的更新,然后就开始写redo日志,这时候处于prepare状态,然后写binlog日志,最后提交事务处于commit状态。所谓的两阶段提交就是将写redo日志分为了prepare和commit两个状态。那么为什么需要两阶段提交呢?我们来看不用两阶段提交会产生什么后果
-
先提交redo log 然后提交binlog,若我们提交redolog后系统崩溃,那么后面的binlog就没有被写入,由于redo日志是完整的,所以可以完全恢复数据库中的数据,但是由于binlog没有写,那么当我们用binlog来备份临时库的时候由于缺少这条binlog而导致和原库数据不一致的情况
-
先提交binlog然后redolog,若我们提交binlog后系统崩溃,由于redolog没有写入所以系统恢复后的数据库缺少该条redolog对应的数据,但是当我们用binlog备份临时库的时候由于binlog存在记录下了系统崩溃前的操作,所以最后得到的临时库与主库中的数据不一致
所以采用两阶段提交主要是为了保证binlog和redolog两者的一致性。