美团面试题:Mysql的redolog和binlog里分别存储的什么内容?
阅读本文前,请您先点击上面的“蓝色字体”,再点击“关注”,这样您就可以每天学习一点新知识,每天都有进步了。
在使用Mysql的时候会接触到三个核心日志,分别是binlog,redo log和undo log。binlog是server层的日志,而redo log和undo log都是引擎层(innodb)的日志,要换其他数据引擎那么就未必有redo log和undo log了。
正是因为它们在Mysql不同的体系结构里,所以他们所针对的的问题也是完全不同的,而我们要了解这些日志的核心思想和功能原理,那么就势必先要从它所针对的问题和设计理念来了解。
binlog
binlog设计目标
binlog是作为mysql操作记录归档的日志,这个日志记录了所有对数据库的数据、表结构、索引等等变更的操作。
也就是说只要是对数据库有变更的操作都会记录到binlog里面来, 可以把数据库的数据当成我们银行账户里的余额,而binlog就相当于我们银行卡的流水。
账户余额只是一个结果,至于这个结果怎么来的,那就必须得看流水了。而同样在mysql里我们就是通过binlog来归档、验证、恢复、同步数据。
binlog记录内容
binlog应该说是Mysql里最核心的日志, 它记录了除了查询语句(select、show)之外的所有的DDL和DML语句,也就意味着我们基本上所有对数据库的操作变更都会记录到binlog里面。
binlog以事件形式记录,不仅记录了操作的语句,同时还记录了语句所执行的消耗的时间。binlog有三种记录格式,分别是ROW、STATEMENT、MIXED。
1. ROW,基于变更的数据行进行记录,如果一个update语句修改一百行数据,那么这种模式下就会记录100行对应的记录日志。
2. STATEMENT,基于SQL句级别的记录日志,相对于ROW模式,STATEMENT模式下只会记录这个update的语句。所以此模式下会非常节省日志空间,也避免着大量的IO操作。
3. MIXED,混合模式,此模式是ROW模式和STATEMENT模式的混合体,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog。
这三种模式需要注意的是:使用row格式的binlog时,在进行数据同步或恢复的时候不一致的问题更容易被发现,因为它是基于数据行记录的。
而使用mixed或者statement格式的binlog时,很多事务操作都是基于SQL逻辑记录,我们都知道一个SQL在不同的时间点执行它们产生的数据变化和影响是不一样的,所以这种情况下,数据同步或恢复的时候就容易出现不一致的情况。
binlog写入策略
在进行事务的过程中,首先会把binlog写入到binlog cache中(因为写入到cache中会比较快,一个事务通常会有多个操作,避免每个操作都直接写磁盘导致性能降低),事务最终提交的时候再吧binlog写入到磁盘中。
当然事务在最终commit的时候binlog是否马上写入到磁盘中是由参数 sync_binlog配置来决定的。
1. sync_binlog=0的时候,表示每次提交事务binlog不会马上写入到磁盘,而是先写到page cache,相对于磁盘写入来说写page cache要快得多,不过在Mysql 崩溃的时候会有丢失日志的风险。
2. sync_binlog=1的时候,表示每次提交事务都会执行fsync写入到磁盘 ;
3. sync_binlog的值大于1的时候,表示每次提交事务都 先写到page cach,只有等到积累了N个事务之后才fsync写入到磁盘,同样在此设置下Mysql崩溃的时候会有丢失N个事务日志的风险。
很显然三种模式下,sync_binlog=1是强一致的选择,选择0或者N的情况下在极端情况下就会有丢失日志的风险,具体选择什么模式还是得看系统对于一致性的要求。
redo log
redo log设计目标
redo log是属于引擎层(innodb)的日志,它的设计目标是支持innodb的“事务”的特性,事务ACID特性分别是原子性、一致性、隔离性、持久性, 一致性是事务的最终追求的目标,隔离性、原子性、持久性是达成一致性目标的手段,根据的文章我们已经知道隔离性是通过锁机制来实现的。而事务的原子性和持久性则是通过redo log和undo log来保障的。
redo log 能保证对于已经COMMIT的事务产生的数据变更,即使是系统宕机崩溃也可以通过它来进行数据重做,达到数据的一致性,这也就是事务持久性的特征。
一旦事务成功提交后,只要修改的数据都会进行持久化,不会因为异常、宕机而造成数据错误或丢失,所以解决异常、宕机而可能造成数据错误或丢是redo log的核心职责。
redo log记录的内容
redo log记录的是操作数据变更的日志,听起来好像和binlog有类似的地方,有时候我都会想有了binlog为什么还要redo log。
当然从其它地方可以找到很多的理由,但是我认为最核心的一点就是redo log记录的数据变更粒度和binlog的数据变更粒度是不一样的,也正因为这个binlog是没有进行崩溃恢复事务数据的能力的。
以修改数据为例,binlog是以表为记录主体,在ROW模式下,binlog保存的表的每行变更记录。
比如update tb_user set age =18 where name ='赵白' ,如果这条语句修改了三条记录的话,那么binlog记录就是
UPDATE `db_test`.`tb_user` WHERE @1=5 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'
UPDATE `db_test`.`tb_user` WHERE @1=6 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'
UPDATE `db_test`.`tb_user` WHERE @1=7 @2='赵白' @3=91 @4='1543571201' SET @1=5 @2='赵白' @3=18 @4='1543571201'
redo log则是记录着磁盘数据的变更日志,以磁盘的最小单位“页”来进行记录。上面的修改语句,在redo log里面记录得可能就是下面的形式。
把表空间10、页号5、偏移量为10处的值更新为18。
把表空间11、页号1、偏移量为2处的值更新为18。
把表空间12、页号2、偏移量为9处的值更新为18。
当我们把数据从内存保存到磁盘的过程中,Mysql是以页为单位进行刷盘的,这里的页并不是磁盘的页,而是Mysql自己的单位,Mysql里的一页数据单位为16K,所以在刷盘的过程中需要把数据刷新到磁盘的多个扇区中去。
而把16K数据刷到磁盘的每个扇区里这个过程是无法保证原子性的,也就意味着Mysql把数据从内存刷到磁盘的过程中,如果数据库宕机,那么就可能会造成一步分数据成功,一部分数据失败的结果。
而这个时候通过binlog这种级别的日志是无法恢复的,一个update可能更改了多个磁盘区域的数据,如果根据SQL语句回滚,那么势必会让那些已经刷盘成功的数据造成数据不一致。所以这个时候还是得需要通过redo log这种记录到磁盘数据级别的日志进行数据恢复。
redo log写入策略
redo lo占用的空间是一定的,并不会无线增大(可以通过参数设置),写入的时候是进顺序写的,所以写入的性能比较高。当redo log空间满了之后又会从头开始以循环的方式进行覆盖式的写入。
在写入redo log的时候也有一个redo log buffer,日志什么时候会刷到磁盘是通过innodb_flush_log_at_trx_commit参数决定。
innodb_flush_log_at_trx_commit=0 ,表示每次事务提交时都只是把redo log 留在redo log buffer 中 ;
innodb_flush_log_at_trx_commit=1,表示每次事务提交时都将redo log直接持久化到磁盘;
innodb_flush_log_at_trx_commit=2,表示每次事务提交时都只是把redo log 写到page cache。
除了上面几种机制外,还有其它两种情况会把redo log buffer中的日志刷到磁盘。
1. 定时处理:有线程会定时(每隔 1 秒)把redo log buffer中的数据刷盘。
2. 根据空间处理:redo log buffer 占用到了一定程度( innodb_log_buffer_size 设置的值一半)占,这个时候也会把redo log buffer中的数据刷盘。
undo log
undo log设计目标
redo log是也属于引擎层(innodb)的日志,从上面的redo log介绍中我们就已经知道了,redo log和undo log的核心是为了保证innodb事务机制中的持久性和原子性,事务提交成功由redo log保证数据持久性,而事务可以进行回滚从而保证事务操作原子性则是通过undo log来保证的。
要对事务数据回滚到历史的数据状态,所以我们也能猜到undo log是保存的是数据的历史版本,通过历史版本让数据在任何时候都可以回滚到某一个事务开始之前的状态。undo log除了进行事务回滚的日志外还有一个作用,就是为数据库提供MVCC多版本数据读的功能。
undo log记录内容
在Mysql里数据每次修改前,都首先会把修改之前的数据作为历史保存一份到undo log里面的,数据里面会记录操作该数据的事务ID,然后我们可以通过事务ID来对数据进行回滚。
比如我们执行update user_info set name =“李四”where id=1的时候,整个undo log的记录形式会如下。
首先准备一张原始原始数据表
2. 开启一个事务A:对user_info表执行 update user_info set name =“李四”where id=1会进行如下流程操作
首先获得一个事务编号104
把user_info表修改前的数据拷贝到undo log
修改user_info表id=1的数据
3. 最后执行完结果如图:
redo、undo、binlog的生成流程与崩溃恢复
当我们执行update user_info set name =“李四”where id=1 的时候大致流程如下:
从磁盘读取到id=1的记录,放到内存
记录undo log日志
记录redo log(预提交状态)
修改内存中的记录
记录binlog
提交事务,写入redo log(commit状态)
我们根据上面的流程来看,如果在上面的某一个阶段数据库崩溃,如何恢复数据。
1. 在第一步、第二步、第三步执行时据库崩溃:因为这个时候数据还没有发生任何变化,所以没有任何影响,不需要做任何操作。
2. 在第四步修改内存中的记录时数据库崩溃:因为此时事务没有commit,所以这里要进行数据回滚,所以这里会通过undo log进行数据回滚。
3. 第五步写入binlog时数据库崩溃:这里和第四步一样的逻辑,此时事务没有commit,所以这里要进行数据回滚,会通过undo log进行数据回滚。
4. 执行第六步事务提交时数据库崩溃:如果数据库在这个阶段崩溃,那其实事务还是没有提交成功,但是这里并不能像之前一样对数据进行回滚,因为在提交事务前,binlog可能成功写入磁盘了,所以这里要根据两种情况来做决定。
如果binlog存在事务记录,那么就"认为"事务已经提交了,这里可以根据redo log对数据进行重做。其实你应该有疑问,其实这个阶段发生崩溃了,最终的事务是没提交成功的,这里应该对数据进行回滚。
这里主要的一个考虑是因为binlog已经成功写入了,而binlog写入后,那么依赖于binlog的其它扩展业务(比如:从库已经同步了日志进行数据的变更)数据就已经产生了,如果这里进行数据回滚,那么势必就会造成主从数据的不一致。
另外一种情况就 是binlog不存在事务记录,那么这种情况事务还未提交成功,所以会对数据进行回滚。
--- THE END ---