mysql|7、mysql的redo log、bin log日志

redo log 简介 **redo log(重做日志)**是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。
比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
注意:redo log是为了恢复buffer pool的数据,防止未刷盘的脏页数据的丢失。
写入时机 mysql|7、mysql的redo log、bin log日志
文章图片

mysql更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。
然后会把**“在某个数据页上做了什么修改”**记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
redo log为物理日志,比如,哪一页做了什么样的修改。
每条 redo 记录由 表空间号+数据页号+偏移量+修改数据长度+具体修改的数据 组成。
刷盘时机 刷盘的时机是根据策略来进行的。

  • InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:
    • 0 : 设置为 0的时候,表示每次事务提交时不进行刷盘操作
    • 1 : 设置为 1的时候,表示每次事务提交时都将进行刷盘操作(默认值)
    • 2 : 设置为 2的时候,表示每次事务提交时都只把redo log buffer内容写入 page cache(磁盘缓存页)
  • InnoDB 存储引擎有一个后台线程,每隔1秒,就会把 redo log buffer 中的内容写到文件系统缓存page cache,然后调用 fsync 刷盘。
  • Redo log Buffer空间不足时,Redo log Buffer的大小是有限的,如果不停的往这个有限大小的Redo log Buffer里散入日志,很快就会被填满,如果当前写入log buffer的redo日志量已经占满了log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
数据的丢失情况:
  • innodb_flush_log_at_trx_commit 为0,刷盘主要由后台线程处理,如果mysql宕机了,有可能会丢失1秒内的数据。
  • innodb_flush_log_at_trx_commit 为1,mysql宕机丢失了也是未提交的事务,不会有什么损失
  • innodb_flush_log_at_trx_commit 为2,mysql宕机丢失了是未提交的事务,不会有什么损失,但是如果是机器宕机了,则有可能会损失1秒内的数据。
日志文件 Mysql的数据目录下默认有两位名为ib_logfile0和ib_logfile1的文件,redo log buffer中的日志默认情况下就是刷新到这两个磁盘文件中,可以通过下边这几个参数来调节:
  • innodb_log_group_home_dir:指定了redo日志所在目录;
  • innodb_log_file_size:指定每个redo日志文件的大小;
  • innodb_log_files_in_group:指定redo日志文件个数,默认值为2,最大值为100;
这些日志文件以日志文件组的形式出现,以ib_logfile[数字] 形式命名,在写入时按照数字顺序写入,如果写入到最后一个文件,那就重新转到ib_logfile[n+1] 继续写。
mysql|7、mysql的redo log、bin log日志
文章图片

总共的redo日志文件大小就是:innodb_log_file_size * innodb_log_files_in_group。
文件格式 将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中,所以redo日志文件起始也是由若干512字节大小的block组成。
每个日志文件组每个文件大小都一样,格式也一样,都是由两部分组成:
  • 前2048个字节,即前4个block用来存储一些管理信息;
  • 从第2048字节往后用来存储redo log buffer中的block镜像。
mysql|7、mysql的redo log、bin log日志
文章图片

mysql|7、mysql的redo log、bin log日志
文章图片

日志文件前2048个字节,也就是前4个特殊block的格式:
  • log file header:描述该redo日志文件的一些整体属性
mysql|7、mysql的redo log、bin log日志
文章图片

  • checkpoint: 记录关于checkpoint的一些属性
mysql|7、mysql的redo log、bin log日志
文章图片

Log Sequeue Number redo日志的序列号:自系统开始运行就不断在修改页面,意味着会不断生成redo日志,redo日志的量在不断递增,为了记录写入的redo日志量,设计了一个称为Log Sequeue Number的全局变量,日志序列号简称为lsn。并且规定初始的lsn值为8704。
系统第一次启动后初始化redo log buffer时,bug_free就会指向第一个block的偏移量为12字节的地方,那么lsn值也会跟着增加12.
mysql|7、mysql的redo log、bin log日志
文章图片

如果某个更新操作产生的一组redo日志占用的存储空间比较小,也就是待插入的block剩余空闲空间能容纳提交的日志时,lsn增长的量就是生成的redo日志占用的字节数。
mysql|7、mysql的redo log、bin log日志
文章图片

如果某个更新操作产生的一组redo日志占用的存储空间比较大,也就是待插入的block剩余空闲空间不足以容纳提交的日志时,lsn增长量就是该mtr生成的redo日志占用的字节数加上额外占用的log block header和logblock trailer的字节数:
mysql|7、mysql的redo log、bin log日志
文章图片

每一组redo日志都有一个唯一的LSN值与其对应,LSN值越小,说明redo日志产生的越早。
flushed _to_disk_lsn 系统第一次启动时,该变量的值何处是的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的值就增长多少。
flush链表中的Log Sequeue Number mysql|7、mysql的redo log、bin log日志
文章图片

当第一次修改某个缓存在Buffer Pool中的页面时,就会把这个页面对应的控制块插入到flush链表的头部,之后再修改该页面时由于它已经在flush链表中了,就不能再次插入了,也就是说flush链表中的脏页是按照页面的第一次修改时间从大到小排序的。在这个过程中会在缓存页对应的控制块中记录两个关于页面何时修改的属性。
  • oldest_modification: 如果某个页面被加载到Buffer Pool后进行第一次修改,那么就将修改该页面开始时对应的lsn值写入这个属性。
  • newest_modification: 每修改一次页面,都会将修改该页面结束时对应的lsn值写入这个属性,也就是说该属性表示页面最近一次修改后对应的系统lsn值。
注意:一个是开始的时候的lsn,一个是更新结束时的lsn。
flush链表中的脏页按照修改时间发生的顺序进行排序,也就是按照oldest_modification代表的LSN值进行排序,被多次更新的页面不会重复插入到flush链表中,但是会更新newest_modification属性的值。
checkpoint 有一个很不幸的事实就是我们的redo日志文件组容量是有限的,所以不得不选择循环使用redo日志文件组中的文件,但这会造成最后写的redo日志与最开始写的redo日志追尾,这时应该想到:redo日志只是为了系统崩溃后恢复脏页用的,如果对应的脏页已经刷新到了磁盘,那么该redo日志也就没有存在的必要了,那么它占用的磁盘空间就可以被后续的redo日志所重用。
也就是说:判断某些redo日志占用的磁盘空间是否可以覆盖的依据就是它对应的脏页是否已经刷新到磁盘里。
设计者提出一个全局变量checkponit_lsn来代表当前系统中可以被覆盖的redo日志总量,这个变量初始值也是8704.
当脏页被刷新到磁盘,部分redo日志就可以被覆盖了,所以我们可以进行增加checkpoint_lsn的操作,这整个过程称之为checkpoint。
做一次checkpoint分为两个步骤:
  • 计算当前系统中可以被覆盖的redo日志对应的lsn值最大是多少,redo日志可以被覆盖,意味着它已经对应的脏页被刷到了磁盘,只要我们计算出当前系统中被最早修改的脏页对应的oldest_modification值,那凡是在系统lsn值小于该节点的oldest_modification值时产生的redo日志都是可以被覆盖掉的,我们就把该脏页的oldest_modification赋值给checkpoint_lsn。
  • 将checkpoint_lsn和对应的redo日志文件组偏移量以及此次checkpoint的编号写到日志文件的管理信息(就是checkpoint1或checkpoint2)中。
设计者还维护了一个目前系统做了多少次checkpoint的变量checkpoint_no,每做一次checkpoint,该变量的值就加1,可以计算得到该checkpoint_lsn在redo日志文件组中对应的偏移量checkpoint_offset,然后把这三个值都写到redo日志文件组的管理信息中。
每一个redo日志文件都有2048个字节的管理信息,但是上述关于checkpoint的信息只会被写到日志文件组的第一个日志文件的关系信息中。
记录完checkpoint信息后,redo日志文件组中各个lsn值得关系就像这样:
mysql|7、mysql的redo log、bin log日志
文章图片

  • checkpoint_lsn之前的日志对应的脏页已经被刷新到磁盘,所以checkpoint_lsn之前的日志文件中的内容可以被覆盖。
  • checkpoint_lsn~flushed_to_disk_lsn之间的日志已经从logbuffer刷新到日志文件中,但是脏页还不能确定有没有被刷盘;
  • flushed_to_disk_lsn~lsn之间的redo日志还留在log buffer中,redo日志没有刷新到文件,并且脏页没有被刷新到磁盘。
那么恢复时如何知道某个redo日志对应的脏页是都在系统崩溃时已经刷盘?
每个页面都有一个称为File Header的部分,在FileHeader中有一个称为FIL_PAGE_LSN的属性,该属性记载了最近一次修改页面时对应的lsn值,如果在做了某次checkpoint后有脏页刷盘,那么该页对应的FIL_PAGE_LSN代表的lsn值肯定大于checkpoint_lsn值,凡是符合这种情况的页面就不需要重复执行lsn值小于FIL_PAGE_LSN的redo日志了。
binlog mysql|7、mysql的redo log、bin log日志
文章图片

binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。
不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。binlog会记录所有涉及更新数据的逻辑操作,并且是顺序写。
记录格式 binlog 日志有三种格式,可以通过binlog_format参数指定。
  • statement: 记录的内容是SQL语句原文,比如执行一条update T set update_time=now() where id=1,记录的内容如下。但是update_time = now() 这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
mysql|7、mysql的redo log、bin log日志
文章图片

  • **row:**记录的内容不再是简单的SQL语句了,还包含操作的具体数据,记录内容如下。row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来。
mysql|7、mysql的redo log、bin log日志
文章图片

update_time=now()变成了具体的时间update_time=1627112756247,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(假设这张表只有 3 个字段)。这样就能保证同步数据的一致性,通常情况下都是指定为row,这样可以为数据库的恢复与同步带来更好的可靠性。
  • mixed: 混合模式,MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式。
写入机制 binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。
因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。
我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。
binlog日志刷盘流程如下
mysql|7、mysql的redo log、bin log日志
文章图片

上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。
上图的 fsync,才是将数据持久化到磁盘的操作。
**刷盘参数配置:**由参数sync_binlog控制,默认是0。
  • 0: 不刷盘,只write,由系统自行判断什么时候执行fsync,性能较高。但是机器宕机,page cache里面的 binglog 会丢失。
  • 1: 表示每次提交事务都会执行fsync,就如同binlog日志刷盘流程一样。
  • N(N>1): 表示每次提交事务都write,但累积N个事务后才fsync刷盘。在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。同样的,如果机器宕机,会丢失最近N个事务的binlog日志。
两段式提交
  • redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。
  • binlog(归档日志)保证了MySQL集群架构的数据一致性。
虽然它们都属于持久化的保证,但是侧重点不同。
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入。
所以redo log与binlog的写入时机不一样。
mysql|7、mysql的redo log、bin log日志
文章图片

redo log日志写入成功,在写binlog日志的时候宕机了,不一致了怎么办?
这种情况会造成主库和从库的数据不一致。
原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
mysql|7、mysql的redo log、bin log日志
文章图片

使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
【mysql|7、mysql的redo log、bin log日志】binlog日志写入成功后,redo log设置commit阶段发生异常,那会不会回滚事务呢?
并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。
mysql|7、mysql的redo log、bin log日志
文章图片

    推荐阅读