MySQL基础篇-日志系统

日志系统:一条SQL更新语句是如何执行的

重要的日志模块:redo log

redo log是InnoDB引擎特有的日志—重做日志

WAL技术,全称Write-Ahead Logging,他的关键点就是先写日志,再写磁盘。

具体来说,当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里面,并更新内存,这个时候就算完成了。同时,InnoDB引擎会是在适当的时候,将这个操作记录更新到磁盘里面。

InnoDB的redo log是固定大小的,比如可以配置伟一组4个文件,每个文件大小都是1GB,从头开始写,写到末尾就又回到开头循环写。

  • write pos 是当前记录的位置,一边写一边后移。
  • check point 是当前要擦除的位置,也是往后推移并循环的。

    中间的位置就是还没有记录的空间

有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

重要的日志模块:binlog

binlog是server层自己的日志—归档日志

因为最开始MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是另一个公司以插件形式引入MySQL的,既然只能依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套体制系统

两种日志的不同

  1. redo log 是InnoDB引擎特有的;binlog是MySQL的server 层实现的,所有引擎都可以使用。
  2. redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑。
  3. redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

执行器与InnoDB引擎在执行update语句时的内部流程

  1. 执行器先找引擎取ID=2这一行,ID是主键,引擎直接用树搜索找到这一行,如果ID=2这一行所有的数据页本来就在内存中,就直接返回给执行器;否则需要先从磁盘读入内存,然后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在是N+1,得到新的一行数据,在调用引擎接口写入这行新数据。
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,可以随时提交事务。
  4. 执行器生成这个操作的binlog,并把binlog写入磁盘。
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log 改成提交 commit状态,更新完成。

两阶段提交

两阶段提交是为了让两份日志之间的逻辑一致。

如何让数据库恢复到半月之内任意一秒的状态
如果需要恢复数据库,那么说明备份系统中会保存近半个月的多有binlog。

  • 首先找到最近一次全量备份,从这个备份恢复到临时库。
  • 从备份的时间点开始,将备份的binlog一次取出来,重新放到中午误删表之前的那个时刻。

此处的commit值的是当前更新语句作为一个事务的commit步骤,不是作为事务的commit语句存在的。

两阶段提交如果在不同时刻,MySQL重启会出现什么现象。

如果是在图中时刻A的地方,也就是写了redo log处于prepare阶段之后、写binlog之前,发生了崩溃,由于此时binlog还没有写,redo log还没有提交,所以奔溃恢复的时候,这个事务会回滚。这时候binlog还没写,所以不会传都备库。

如果时刻B发生了奔溃,也就是binlog写完,redo log还没commit前发生。

  1. 如果redo log里面的事务是完整的,也就是已经有了commit标识,直接提交;
  2. 如果redo log里面的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:
    • 如果是,则提交事务;
    • 否则,回滚事务。

MySQL怎么知道binlog是完整的?

一个事务的binlog是有完整格式的:

  • sratement格式binlog,最后会有commit;
  • row格式的binlog,最后会有一个XID event。
    在MySQL5.6.2版本以后,还引入了binlog-checksum参数,用来验证binlog内容的正确性。对于binlog日志由于磁盘原因,可能会在日志中间出错误的情况,MySQL可以通过校验checksum的结果来发现。

redo log 和binlog 是怎么关联起来的?

它们有一个共同的数据字段,XID,崩溃恢复的时候,会顺序扫描redo log

  • 如果碰到既有prepare、又有commit的redo log,就直接提交。
  • 如果碰到只有prepare、而没有commit的redo log,就拿着XID去binlog找对应的事务。

处于prepare阶段的redo log加上完整binlog,重启就能恢复,MySQL为什么要这么设计?

采用这样的策略可以让主库和备库的数据保证一致性。

如果是这样的话,为什么还要两阶段提交呢?干脆先redo log写完,再写binlog。崩溃恢复的时候,必须得两个日志都完整才可以。

两阶段提交是经典的分布式系统问题。

这个问题的是事务的持久性问题。

对于InnoDB引擎来说,如果redo log提交完成,事务就不能回滚了(如果回滚就可能覆盖掉别的事务的更新)。而入伏哦redo log直接提交,然后binlog写入失败。InnoDB又回滚不了,数据和binlog日志就不一致了。

不引入两个日志,也就没有两阶段提交的必要了。只用binlog来支持崩溃恢复,又能支持归档,不久可以吗?

binlog没有能力恢复“数据页”。

如果图中标的位置,也就是binlog2写完了,但是整个事务还没有commit的时候,MySQL发生了crash。

重启后,引擎内部事务2会回滚,然后应用binlog2可以不回来;但是对于事务1来说,系统已经认为提交完成了,不会再应用一次binlog1。

但是InnoDB引擎使用的是WAL技术,执行事务的时候,写完内存和日志,事务就算完成了,如果之后崩溃,要依赖日志来恢复数据页。

也就是说,在图中位置发生崩溃的话,事务1也是可能丢失了的,而且是数据页级的丢失。

那能不能只用redo log 不用binlog?

如果是从崩溃的角度来讲是可以的,这样就没有两阶段提交了,但系统依然是crash-safe的。

binlog 有归档的功能,redo log是一个循环写,日志无法完整保留。

MySQL系统依赖与binlog。binlog作为MySQL一开始就有的功能,被用在了很多地方,MySQL系统高可用的基础,就是binlog复制。主备–主从

redo log 一般设置多大

直接将redo log设置为4个文件、每个文件1GB吧。这个TB的磁盘。

正常运行中的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的?

  1. 如果正常运行的实例,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘。这个过程与redo log毫无关系。
  2. 在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复的时候丢失了更新,就会将它读到内存,然后redo log更新内存内容。更新完后,内存页变成脏页,就回到了第一种情况的状态。

redo log buffer是什么,?是先修改内存,还是先写redo log文件?

1
2
3
4
5

begin;
insert into t1 ...
insert into t2 ...
commit;

这个事务要往两个表中插入记录,插入数据的过程中,生成的日志都得先保存起来,但又不能在还没commit的时候就直接写到redo log文件里。

所以 redo log buffer就是一块内存,用来先存redo日志的。也就是说,在执行第一个insert的时候,数据的内存被修改了,redo log buffer页写入了日志。

但是,真正把日志写到redo log文件(文件名是ib_logfile+数字),是在执行commit语句的时候做的。