**redo log(重做日志)**是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。
比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
注意:redo log是为了恢复buffer pool的数据,防止未刷盘的脏页数据的丢失。
mysql更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。
然后会把**“在某个数据页上做了什么修改”**记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
redo log为物理日志,比如,哪一页做了什么样的修改。
每条 redo 记录由 表空间号+数据页号+偏移量+修改数据长度+具体修改的数据 组成。
刷盘的时机是根据策略来进行的。
数据的丢失情况:
Mysql的数据目录下默认有两位名为ib_logfile0和ib_logfile1的文件,redo log buffer中的日志默认情况下就是刷新到这两个磁盘文件中,可以通过下边这几个参数来调节:
这些日志文件以日志文件组的形式出现,以ib_logfile[数字] 形式命名,在写入时按照数字顺序写入,如果写入到最后一个文件,那就重新转到ib_logfile[n+1] 继续写。
总共的redo日志文件大小就是:innodb_log_file_size * innodb_log_files_in_group。
将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中,所以redo日志文件起始也是由若干512字节大小的block组成。
每个日志文件组每个文件大小都一样,格式也一样,都是由两部分组成:
日志文件前2048个字节,也就是前4个特殊block的格式:
redo日志的序列号:自系统开始运行就不断在修改页面,意味着会不断生成redo日志,redo日志的量在不断递增,为了记录写入的redo日志量,设计了一个称为Log Sequeue Number的全局变量,日志序列号简称为lsn。并且规定初始的lsn值为8704。
系统第一次启动后初始化redo log buffer时,bug_free就会指向第一个block的偏移量为12字节的地方,那么lsn值也会跟着增加12.
如果某个更新操作产生的一组redo日志占用的存储空间比较小,也就是待插入的block剩余空闲空间能容纳提交的日志时,lsn增长的量就是生成的redo日志占用的字节数。
如果某个更新操作产生的一组redo日志占用的存储空间比较大,也就是待插入的block剩余空闲空间不足以容纳提交的日志时,lsn增长量就是该mtr生成的redo日志占用的字节数加上额外占用的log block header和logblock trailer的字节数:
每一组redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早。
系统第一次启动时,该变量的值何处是的lsn的值是相同的,都是8704,随着系统的运行,redo日志被不断写入reodo log buffer 但是并不会立即刷新到磁盘,lsn的值就和flashed_to_disk_lsn的值拉开了差距。
当新的redo日志写入到log buffer时,首先lsn的值会增长,但是flushed_to_disk_lsn不变,随后随着不断有log buffer中的日志被刷新到磁盘上,flushed_to_disk_lsn的值也跟着增长,如果两者的值相同,说明log buffer中的所有redo日志都已经刷新到磁盘中了。
初始时的flushed_to_disk_lsn值是8704,对应文件偏移量2048,之后每个mtr向磁盘中写入多少字节的日志,flushed_to_disk_lsn的值就增长多少。
当第一次修改某个缓存在Buffer Pool中的页面时,就会把这个页面对应的控制块插入到flush链表的头部,之后再修改该页面时由于它已经在flush链表中了,就不能再次插入了,也就是说flush链表中的脏页是按照页面的第一次修改时间从大到小排序的。在这个过程中会在缓存页对应的控制块中记录两个关于页面何时修改的属性。
注意:一个是开始的时候的lsn,一个是更新结束时的lsn。
flush链表中的脏页按照修改时间发生的顺序进行排序,也就是按照oldest_modification代表的LSN值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值。
有一个很不幸的事实就是我们的redo日志文件组容量是有限的,所以不得不选择循环使用redo日志文件组中的文件,但这会造成最后写的redo日志与最开始写的redo日志追尾,这时应该想到:redo日志只是为了系统崩溃后恢复脏页用的,如果对应的脏页已经刷新到了磁盘,那么该redo日志也就没有存在的必要了,那么它占用的磁盘空间就可以被后续的redo日志所重用。
也就是说:判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里。
设计者提出一个全局变量checkponit_lsn来代表当前系统中可以被覆盖的redo日志总量,这个变量初始值也是8704.
当脏页被刷新到磁盘,部分redo日志就可以被覆盖了,所以我们可以进行增加checkpoint_lsn的操作,这整个过程称之为checkpoint。
做一次checkpoint分为两个步骤:
设计者还维护了一个目前系统做了多少次checkpoint的变量checkpoint_no,每做一次checkpoint,该变量的值就加1,可以计算得到该checkpoint_lsn在redo日志文件组中对应的偏移量checkpoint_offset,然后把这三个值都写到redo日志文件组的管理信息中。
每一个redo日志文件都有2048个字节的管理信息,但是上述关于checkpoint的信息只会被写到日志文件组的第一个日志文件的关系信息中。
记录完checkpoint信息后,redo日志文件组中各个lsn值得关系就像这样:
那么恢复时如何知道某个redo日志对应的脏页是都在系统崩溃时已经刷盘?
每个页面都有一个称为File Header的部分,在FileHeader中有一个称为FIL_PAGE_LSN的属性,该属性记载了最近一次修改页面时对应的lsn值,如果在做了某次checkpoint后有脏页刷盘,那么该页对应的FIL_PAGE_LSN代表的lsn值肯定大于checkpoint_lsn值,凡是符合这种情况的页面就不需要重复执行lsn值小于FIL_PAGE_LSN的redo日志了。
binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。
不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。binlog会记录所有涉及更新数据的逻辑操作,并且是顺序写。
binlog 日志有三种格式,可以通过binlog_format参数指定。
update T set update_time=now() where id=1
,记录的内容如下。但是update_time = now() 这里会获取当前系统时间,直接执行会导致与原库的数据不一致。update_time=now()变成了具体的时间update_time=1627112756247,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(假设这张表只有 3 个字段)。这样就能保证同步数据的一致性,通常情况下都是指定为row,这样可以为数据库的恢复与同步带来更好的可靠性。
binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。
因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。
我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。
上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。
上图的 fsync,才是将数据持久化到磁盘的操作。
**刷盘参数配置:**由参数sync_binlog控制,默认是0。
redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。
binlog(归档日志)保证了MySQL集群架构的数据一致性。
虽然它们都属于持久化的保证,但是侧重点不同。
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入。
所以redo log与binlog的写入时机不一样。
redo log日志写入成功,在写binlog日志的时候宕机了,不一致了怎么办?
这种情况会造成主库和从库的数据不一致。
原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
binlog日志写入成功后,redo log设置commit阶段发生异常,那会不会回滚事务呢?
并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。