MySQL日志之redo log与bin log

欢迎关注我的公众号:"阿东编程之路"!

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值