脏页既是对一条记录进行更新时,innodb将该记录所在的页读取到内存,然后在内存中对该数据进行更新。此时内存页中数据有修改,且还没有写入到文件中,此时的内存页就叫做脏页。
innodb不会将内存页中修改的数据立即刷新到文件中,而是将写操作产生的变化以日志的形式记录到redo log文件中,当服务意外崩溃重启时,会读取redo log,对没有写入文件的数据进行恢复。
innodb不会将内存页中修改的数据立即刷新到文件中的原因是写redo log是顺序写入,不会产生随机io。随机io将导致磁盘频繁寻道,写入性能很低。
所有这些脏页会在内存中串成一个链表,以每个页首次被修改的时间为顺序排列,最早被修改的页位于链表尾部。
redo log也称作重做日志,redo log将所有的写操作以规定的格式顺序记录到redo日志中。例如一条insert语句的redo log将包含表空间id、插入的记录所在的页号、insert的具体数据。
redo log也不是一旦有新的日志就写入文件,innodb为redo log在内存开辟了一个redo log buffer,新的日志先写入这个内存中,在合适的时机再将新的日志刷盘到redo log文件中。这个刷盘时机有如下几种:
当redo日志量已经占满了log buffer总容量的50%左右,就需要把这些日志刷新到磁盘中
事务提交时,1个事务产生的所有redo日志写入成功才认为是事务提交成功。
后台有一个线程,大约以每秒一次的频率将log buffer中的日志刷新到磁盘
正常关闭服务时
做checkpoint时
另外事务提交时是否立即将redo log刷盘,由innodb_flush_log_at_trx_commit变量控制
0:事务提交时不立即向磁盘同步redo日志,由后台线程来处理
1:事务提交时将redo日志同步到磁盘,默认值
2:事务提交时需要将redo日志写入操作系统的缓冲区中,不需要刷盘
对底层页面进行一次原子性操作的过程称为一个Mini-Transaction(MTR)。
例如对一个索引进行插入,可能遇到需要页面分裂,涉及到很多操作:新增页面、复制数据、修改目录项等等,这些操作必须要么全部恢复要么全部不恢复,否则将出现逻辑性的错误。一次MTR最终可能产生很多条redo日志记录,这些记录必须作为一个组来进行恢复。

LSN全称是log sequence number。lsn的值代表了系统从首次启动到目前,写入的redo日志量的总大小。每当产生一个redo日志记录,lsn值就增加这个redo日志记录的大小(其实应该是每产生一个MTR,lsn值增加这个MTR中包含的日志记录的大小)。
lsn值一方面是递增且唯一的,另一方面某一时刻的lsn值指向了当时redo日志的写入位置。
heckpoint是使用redo log进行数据恢复时,识别出需要对哪些redo log进行恢复的关键。
当出现服务崩溃,再重启时,那些需要恢复的数据其实就是在服务崩溃时还在脏页中没有刷新到文件的数据。
在dirty page小节最后,我们介绍了一个脏页链表,位于链表尾部的脏页是最早被修改的脏页,每个脏页还有一个属性,oldest-modification——存储了这个页首次被修改前的lsn值。
innodb进行checkpoint时,将脏页链表尾部的oldest-modification值写入到redo文件的控制信息部分的LOG_CHECKPOINT_LSN中。进行数据恢复时,innodb将从LOG_CHECKPOINT_LSN所指向的redo文件位置开始进行数据恢复。
由于checkpoint和脏页刷新是不同的任务,有可能一次checkpoint完成后,在下一次checkpoint之前,有些脏页刷新到了文件。对于这种情况如果单纯从LOG_CHECKPOINT_LSN指向的位置开始进行数据恢复,将导致那些checkpoint之后已经刷新到文件的脏页对应的redo log重放,产生数据错误。
每个脏页还有另外一个属性,newest-modification——存储了这个页最近1次被修改后的lsn值。脏页刷新到文件时,会将这个值写入到页的FIL_PAGE_LSN属性中。进行数据恢复时,将对比页的FIL_PAGE_LSN属性和redo日志记录中的lsn值,如果FIL_PAGE_LSN较大,说明页中的数据比redo日志新,则跳过这条redo日志的恢复。
下面给出一个图示,方便理解:


但是和 Select最大不同的是,Update语句会涉及到两个日志的操作 redo log(重做日志) 和 binlog (归档日志)。
大家都是知道 Mysql是关系型数据库,用来存储数据的,在访问数据库量大的时候,Mysql读写磁盘访问的效率是非常低的,加上 sql中的条件对数据的筛选过滤,那么效率就更低了。这也是为什么引入非关系型数据库作为作为数据缓存原因,例如:Redis、MongoDB等,就是为了减少 sql执行期间的数据库 io操作。
同样的道理,若是每次执行 update语句都要进行磁盘 io操作、以及数据的过滤筛选,小量的访问和数据量数据库还可以撑住,那么访问量一大以及数据量一大,这样数据库肯定顶不住。基于上面的问题于是出现了redo log日志,redo log日志也叫做 WAL技术(Write-Ahead Logging),他是一种先写日志,并更新内存,最后再更新磁盘的技术,并且更新磁盘往往是在 Mysql比较闲的时候,这样就大大减轻了 Mysql的压力。
redo log的特点就是:redo log是固定大小,是物理日志,属于 InnoDB引擎的,并且写 redo log是环状写日志的形式:

如上图所示:若是四组的redo log文件,一组为1G的大小,那么四组就是4G的大小,其中 write pos是记录当前的位置,有数据写入当前位置,那么write pos就会边写入边往后移。而 check point是擦除的位置,因为redo log是固定大小,所以当redo log满的时候,也就是 write pos追上 check point的时候,需要清除 redo log的部分数据,清除的数据会被持久化到磁盘中,然后将 check point向前移动。redo log日志实现了即使在数据库出现异常宕机的时候,重启后之前的记录也不会丢失,这就是 crash-safe能力。
binlog
binlog称为归档日志,是逻辑上的日志,它属于 Mysql Server层面的日志,记录着 sql的原始逻辑,主要有两种模式,一个是 statement格式记录的是原始的sql,而 row格式则是记录行内容。那么这样看来 redo log和 binlog虽然记录的形式、内容不同,但是这两者日志都能通过自己记录的内容恢复数据,那么为什么还要这两个日志同时存在呢?只要其中一个不就行了嘛,两个同时存在不就多此一举了嘛。且听我慢慢道来,这里面也大有文章。
因为刚开 Mysql自带的引擎 MyISAM就没有 crash-safe功能的,并且在此之前 Mysql还没有 InnoDB引擎,Mysql自带的 binlog日志只是用来归档日志的,所以 InnoDB引擎也就通过自己 redo log日志来实现 crash-safe功能。