暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL日志之redo log与bin log

阿东编程之路 2022-05-04
531

MySQL中有很多重要的日志,比如redo log和bin log,在数据的增删改都会涉及这两个日志的写入,今天我们来了解下redo log和binlog。


一. redo log(重做日志)


打个比方,现在有个酒馆,阿东是酒馆掌柜,阿东有个粉板来记录客人的赊账和还账,还有个用来记录赊账的账本,那现在如果有个叫张三的客人要赊账,阿东怎么去做?


1. 把账本翻出来,根据张三找到他的赊账记录,并把这次赊的账加上或扣除


2. 直接记录张三的姓名和赊账金额在粉板上,等打烊或空闲时再去做核算。


  • 如果是第一种,在酒馆忙的时候肯定不行,阿东在客人点菜赊账后还需要去翻出账本去查找赊账人和核算,效率肯定非常低,所以我们可以把耗时的工作放在后面做;所以我们可以用第二种方式,先在粉板上按照顺序记录赊账人的姓名和金额,然后就可以继续接待客人了,等打烊或空闲时再去做核算,省去随机查找赊账账本的时间和核算的时间


  • 其实MySQL也是同样的道理,赊账或者还账的行为就可以看作是更新数据库的操作,赊账账本就是MySQL磁盘的数据,如果每次更新都直接操作磁盘,就会有很高的查找成本(查找赊账人)和IO成本(核算),所以MySQL就会先更新内存(buffer pool),然后记录到redo log。


那这里你肯定会问,更新的时候更新到内存中等到机器空闲时直接刷盘就可以了,为什么还需要再写redo log呢?


  • MySQL刷盘确实是从内存中直接刷到磁盘的,但是如果MySQL出现异常宕机或停电了,内存中的数据就丢失了,相当于丢数据了,正常的业务肯定都不能容忍,所以,内存更新后还需要持久化到redo log中,等到异常重启后可以通过redo log恢复内存中还没刷盘的脏页,保证数据的可靠性,对应着阿东的酒馆,如果阿东只是在脑子里(内存)记着,时间长可能会忘记(这个可以看作crash),所以需要通过粉板的记录来恢复记忆(恢复内存数据)。


有了redo log,InnoDB(这里说一下,redo log是innodb引擎特有的)就可以保证出数据也不会丢失,这种能力叫做crash-safe


那如果赊账的人太多,阿东的赊账粉板写不下了怎么办?


  • 如果粉板写不下了,阿东只能停下手中的事情,去将一部分的粉板内容核算到账本上,然后擦掉粉板上核算完的内容,为新的赊账腾出空间。


  • 正所谓技术来源于生活,InnoDB也是这么做的,redo log的大小是固定的,但是也是可配置的,比如配置一组四个文件,每个文件一个G,就可以存放4GB的内容,文件类似下面环形:

图片出自著名腾讯数据库负责人林晓斌老师


  • write pos是当前记录的位置,从0号文件开始写,边写边往后移动,直到写到3号文件末尾,再写就写到0号文件开头,check point是当前擦除的位置,也是和write pos一样循环往后移动,check point擦除记录前需要把内存中的脏页刷盘,所以图中write pos和check point中绿色的空间就是可写的空间,如果write pos写到check point的位置,就代表需要停下来刷个盘,check point的位置也会往前移,这样就又有可写空间了,类似滑动窗口算法的思想。


上述擦除脏页刷盘的操作我们叫做flush。flush操作需要停下写redo log的操作去刷盘肯定会导致语句响应变慢的,那除了redo log文件没空间还有哪些情况会导致flush呢?


在此之前我们先了解下buffer pool的一些概念:


InnoDB用缓冲池(buffer pool)来管理内存,内存中的数据都是以页(16kb)的形式进行管理,缓存池中的内存页有三种状态:


  1. 第一种,还没有使用的空闲页


2. 第二种,使用了并且是干净页(干净页就是内存数据写入磁盘后,内存页的数据和磁盘上的数据页内容一致)。


3. 第三种,使用了并且是脏页(脏页就是内存数据还未写入磁盘,内存页的数据和磁盘上的数据页内容不一致)。


下面我们来看下哪些情况会导致flush:


1. 内存不足


  • 内存不足时,当需要新的内存页时,就需要淘汰掉一部分内存页,如果淘汰的是脏页,就需要先flush再淘汰。


2. 系统空闲时


  • MySQL会监控的系统的读写qps,当mysql认为系统空闲时就会去flush。


3. Mysql的正常关闭


  • MySQL正常关闭时会把内存中的脏页都flush进磁盘。


所以redo log的作用就是来防止数据库崩溃内存脏页丢失的。



二. binlog(归档日志)


MySQL更新时会写redo log,同样会涉及到另一个重要日志的更新 - binlog。


binlog在MySQL中的地位非常重要的,作用也非常多:


  1. 主从同步就是根据从库消费的binlog增量来进行同步的;


2. 可以用bin log来恢复误删除的数据。


  • MySQL其实整体分为两块,server层和引擎层,上面我们说的redo log其实是InnoDB引擎特有的日志,而binlog是server层面的日志,是所有引擎共享的。


redo log和binlog 有什么区别?


  1. 实现不同:redo log是InnoDB引擎特有的,binlog是server层面的,所有引擎都可以使用。


2. 内容不同:redo log记录的是物理日志,记录的是在某个数据页上修改了什么内容(MySQL内存中的数据都是以为单位进行存储);binlog是逻辑日志,记录的是sql语句的原始逻辑(binlog记录的内容有两种模式,下面详细讲下)。


3. 大小不同:redo log是循环写且大小固定,bin log是追加写,没有大小限制,一个文件写满就新写一个文件。


binlog有哪几种格式?


binlog的格式有三种:statement,row,mixed(statement和row模式的结合)。


看binlog格式的命令:

    show variables like "binlog_format";


    innoDB默认是row格式:


    • statement模式下的binlog存就是sql语句;


    • row模式下的新增和删除会记录操作数据的id,更新会记录更新前和更新后的数据。


    一般生产环境用的都是row模式,为啥不用statement模式呢,用statement模式下同样的sql所占文件更小啊?


    statement模式在主从同步的时候会出现主从数据不一致的问题的,比如下面这个语句:

      -- 表t,a和b都为普通索引字段 
      delete from t where a > 3 and b < 40 limit 1;


      • 假如在主库执行时走的是索引a,删除的是a索引树上从左往右大于3并且满足b<40的第一个数据,并将binlog二进制数据发给从库,从库解析成statement格式语句并执行(中间还有relay log的步骤先暂时省略),这时有可能又走到索引b上,删除索引树上从右往左小于40并满足a>3的第一个数据,这两个数据基本上不可能是同一个,所以就会造成主从库的数据不一致


      • 如果用row模式的话,刚才说了,row模式下的所有操作都会带有id,所以就不会因为mysql优化器选错索引导致主从库不一致了,所以一般我们都会去设置binlog为row模式。


      三. redo log和binlog的一致性


      redo log两阶段提交


      介绍完redo log和bin log后,你是否会有这样一个问题:如果要写操作要操作redo log和binlog两个日志,那怎么保证这两个日志的一致性呢?


      redo log使用两阶段提交来保证redo log和binlog的一致性。

        update t set a = 2 where id = 1;


        就上面这个更新语句我们来看一下redo log的两阶段提交是怎么做的:


        • 我们看到写redo log是先写prepare状态,接着去写binlog,最后再写redo log的commit状态事务的提交就算完成了。


        我们来分析下在不同时间点机器crash了,redo log的两阶段是怎么保证redo log和binlog的一致性的。


        • 时刻1和时刻2我们一起说下,当在时刻1或时刻2时mysql宕机了,重启后发现redolog中只有prepare状态没有commit,会根据对应事务id去binlog里(在binlog中还会记录事务commit和XID)查找该事务是否完整,如果完整就写redo log commit状态,不完整就回滚事务。


        • 时刻3发现redo log commit完整,就不用去判断binlog,直接完成事务的最终提交。


        这样的逻辑就会保证mysql在不同时刻宕机都能保证redo log和binlog的一致性。


        这个时候你肯定会想,这么麻烦为什么不直接就用binlog来恢复异常重启后内存中丢失的脏页呢?


        • 其实这里面是有很多原因的,有个历史原因:以前MySQL是没有InnoDB引擎的,所以也没有redo log,是后面以插件的形式加入MySQL,之前binlog的定位一直就是备份冗余和主从同步,要做成redo log的功能就需要改很多东西,比如binlog没有redo log中的标记flush的check point指针所以恢复数据时无法定位到具体位置;还有一个物理原因,redo log记录的是物理日志,就是解析后的sql语句,是直接操作内存数据页面的,重启恢复数据的速度也会比binlog快很多。



        四. redo log和bin log的写入机制


        • 尽管redo log和binlog是顺序写,但是也不是直接落盘的,它们也是先写到各自的buffer中,然后会去write到文件系统的page cache中(这个操作你可以理解写到操作系统的缓存中,没有落盘),最终调用fsync才算最终落盘。


        binlog的write和fsync的时机是由sync_binlog控制的:


        1.  sync_binlog = 0时,表示每次提交事务只write,不fsync;


        2. sync_binlog = 1时,表示每次提交事务都会fsync;


        3. sync_binlog = N(N>1)时,表示每次提交都write,但是积累N个事务后才fsync。


        • 这一块的知识请教过公司的DBA大佬,设置0的时候,只有操作系统的后台线程定时去fsync,丢失的日志数量不可控;1是最安全的,每次提交都会fsync,但也是性能最差的,因为每次都要写盘;所以就要看业务能接受多少数据量的丢失,我们线上设置的是200,其实我理解就是一个取舍,如果想要数据不丢失就要舍掉一部分性能。



        而redo log的写入机制是innodb_flush_log_at_trx_commit配置控制的:


        1. 设置为0的时候,表示每次事务提交时都只是把redo log 留在redo log buffer 中;


        2. 设置为1的时候,表示每次事务提交时都fsync


        3. 设置为2的时候,表示每次事务提交时都write


        • 在InnoDB后台也有一个线程每隔1s去清redo log buffer,去write到文件系统并fsync落盘。设置0的话肯定是不可控的丢失redo log量的,设置1的话也是和binlog类似每次提交事务都fsync吞吐量不行的,所以我们线上都会选择设置2。


        • 其实redo log和bin log的写入还是看取舍,如果你们的业务非常敏感,一点数据都不能丢,那就设置sync_binlog和innodb_flush_log_at_trx_commit为“双一”,自然会牺牲性能;想要性能或者遇到io瓶颈的话,可以尝试sync_binlog设置100~1000,innodb_flush_log_at_trx_commit设置2。


        ps:redo log和bin log的写入还有组提交的优化,就是类似合并请求的思想,大家感兴趣可以私下去了解下。



        五. 总结:


        今天我们了解了重做日志redo log和bin log的作用、原理和区别、flush的时机、redo log和binlog怎么保证一致性,以及它们的写入机制等。后面会讲下mysql的事务以及锁,大家想了解哪方面的知识可以私信我。


        如果觉得文章不错可以给个关注和赞



        参考书籍及文章:

        1. 《MySQL实战45讲》 作者:林晓斌

        2. 《深入理解MySQL核心技术》 作者:Sasha Pachev


        文章转载自阿东编程之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论