MySQL日志机制

MySQL日志机制

在各种系统中,凡是要使用什么,我们都要为其建立一个数据结构,借助数据结构,我们能更深入的了解。

MySQL中有几种非常重要的日志:

  • binlog:归档日志,MySQL的server层实现,所有引擎都可以使用,是逻辑日志,记录的是数据操作的原始逻辑。
  • redo log:重做日志,InnoDB引擎实现,是物理日志,记录的是在某个数据页上的操作。
  • undo log:回滚日志,MySQL的server层实现,是逻辑日志,记录的是数据操作的反逻辑,如:delete一条记录,binlog记录的是delete了一条记录,而undo log记录的是insert了一条记录。

三种日志的配合使用,才能实现事务机制、数据恢复、主备同步等功能,是MySQL中非常重要的一个模块。

两阶段提交

事务在提交时,先写入一个处于prepare的redo log,再写入一个binlog,最后再写入一个处于commit的redo log。

为什么需要两阶段提交?

  • 两阶段提交是一个经典的分布式问题,当每个人都确认自己完成的时候,再一起提交。对于binlog和redo log来说,

  • 若redo log提交完成,事务就不能回滚,否则可能会覆盖别的事务更新;但若redo log直接提交,binlog在写入的时候失败了,事务不能回滚,导致数据和binlog不一致。

WAL机制

WAL:Write Ahead Logging,核心思想就是在操作数据时,记录下操作数据的日志(redo log和binlog),并修改内存中对应的数据,此时该操作就完成了,在适当的时机,MySQL再将该数据更新到磁盘中。

WAL减少了磁盘写的次数,得益于两方面:

  • redo log和binlog都是顺序写,顺序写比随机写的速度要快。
  • redo log和binlog都有组提交机制,可以进一步的减少IOPS消耗。

binlog

在binlog的数据结构中,XID、GTID、server_id以及记录的数据操作主体(由多个字段组成)是我们需要特别注意的几个字段。

  • XID:借助这个字段,binlog可以和redo log关联起来,在数据恢复时,配合使用。

    崩溃恢复时,会按顺序扫描redo log。碰到既有prepare和commit状态的两个redo log,就直接提交;只碰到prepare状态、没有commit状态的单个redo log,拿着redo log的XID去寻找是否对应的binlog。

  • GTID:全局事务ID,是事务在提交时生成的唯一标识,可用于在从库中找新主库的同步位置、判断主备库是否有延迟。

  • server_id:记录了该操作第一次执行时所在的MySQL实例ID,可用于解决主备一致中循环复制的问题。

  • 数据操作主体内容:binlog有row、statement、mixed三种记录数据操作的格式。

binlog的完整性保证:

  • binlog具有完整的格式,statement格式的binlog最后会有commit,row格式的binlog最后有XID event。

  • 并且MySQL 5.6.2版本后引入了binlog-checksum参数,用于验证binlog的正确性。

三种格式

statement:记录的是SQL语句的原文。这种方式占据空间小,但有可能会导致主备不一致。

row:记录了SQL语句操作行的所有字段,如delete语句就会记录所有字段的值,保证了主备库的操作都是一致的。但这种方式binlog占的空间会很大,比如我delete了10G的数据,那么binlog中就会记录10G的数据,也要耗费对应的IO资源。

mixed:statement和row格式混用,MySQL判断当前SQL语句是否会导致主备不一致,若是,则使用row,否则使用statement。

看似mixed方式很好,但是在数据恢复的时候,若binlog是row格式,直接解析binlog数据并应用其反逻辑即可,但有些SQL语句依赖上下文命令,若直接使用statement的binlog恢复,执行结果很有可能是错误的。并且,在主备同步的时候,使用row格式的binlog会使得主备数据不一致的问题更容易被发现。

所以,binlog的格式最好还是设置为row。

写入机制

binlog是追加写,binlog文件写到一定大小之后会切换到下一个文件,不会覆盖之前的binlog。

写入过程:在事务的执行期间,先把binlog写入到binlog cache,等事务提交的时候,(write)再把binlog cache中的binlog写入到binlog文件(位于文件系统的page cache)中,清空binlog cache,并根据sync_binlog的设置,(fsync,此时才认为会消耗IO资源)将binlog持久化到磁盘中。

  • 一个事务的binlog不能拆开,无论事务多大,都要确保一次性写入。
  • 一个线程一个binlog cache,若事务大小超过了binlog cache大小,就要把binlog暂存到磁盘中。
  • 多个线程共用一个binlog文件。
  • sync_binlog = N。N=0,每次提交事务只write,不fsync;N≠0,每次提交事务都会write,但累积N个事务才会fsync。在IO瓶颈时,可以将N值设置得大一些,但缺点就是若主机发生异常重启,会丢失N个事务的binlog。

数据恢复

在进行数据恢复时,需要使用全量备份+binlog来恢复数据。数据恢复的流程:

  1. 取最近一次的全量备份,用备份恢复出一个临时库;
  2. 从日志备份中,取出全量备份之后的binlog和处于prepare的redo log,将这些binlog应用到临时库中,若有误删的语句,就不用应用到临时库了(跳过日志,可以使用GTID,或使用stop/start-position的方式)。

redo log

redo log使得MySQL具有crash-safe能力,在数据库异常重启时,之前提交的记录也不会丢失。

崩溃恢复时,我们取出所有redo log:

  • 若redo log里的事务完整,即有commit和prepare两个完整的redo log,那么直接提交该事务。

  • 若redo log只有完整的redo log,判断binlog是否存在并完整:

    • 若是,则直接提交事务,binlog已经写入了,会被从库使用,此时必须要提交事务,保证数据一致;
    • 否则,利用undo log回滚事务。

redo log记录的是数据页上做出的修改,只有一种格式,但redo log有prepare、commit两个状态。

写入机制

redo log是循环写,空间是固定的,wirte pos是当前记录的位置,checkpoint是当前要擦除记录的位置,擦除记录就是将记录更新到文件中。当write pos追上checkpoint时,代表redo log已经写满了,需要将记录更新到文件中,触发刷脏页flush。

redo log写满时,会停止库的所有更新操作,将redo log的checkpoint向前推进,以留出一定空间。根据所设置的脏页比例上限,确定要刷脏页的数量,全力刷脏页。

redo log的写入机制和binlog的类似,redo log有一个用于暂存redo log的redo log buffer,也具有write和fsync两个操作。redo log写入的策略由innodb_flush_log_at_trx_commit参数控制:

  • 值=0时,每次事务提交都只把redo log留在redo log buffer中;
  • 值=1时,每次事务提交时都将redo log直接持久化到磁盘,根据数据恢复的逻辑,InnoDB只需要将prepare的redo log持久化到磁盘中即可,commit的redo log只需要write到fs page cache;
  • 值=2时,每次事务提交都只把redo log写到page cache中。

较binlog不同的是,innodb有一个每隔1秒就把redo log buffer中的所有日志(可能包含未提交的事务的redo log)write到文件系统的page cache中、然后fsync到磁盘中的后台线程。

组提交

LSN:日志逻辑序列号,对应redo log的写入点,每次写入长度为length的redo log,LSN的值都会加上length。

LSN和checkpoint组合起来,保证数据页不会被多次重复执行。

MySQL为减少写盘次数,引入了组提交机制(此时innodb_flush_log_at_trx_commit = 1)。当redo log buffer中的事务(该组的leader)要fsync时,会将小于当前LSN的所有redo log都fsync到磁盘中,其他组员就可以直接返回了。binlog也有这个机制。

在并发的情况下,第一个事务写完redo log buffer以后,该事务prepare的redo log fsync得越晚,组员就越多,节约IOPS的效果就越好。

因此,MySQL延迟了两阶段提交中redo log的fsync,两阶段提交的具体过程为:prepare的redo log write;binlog write;prepare的redo log write;binlog fsync;commit的redo log write。

undo log

每天记录更新的时候,都会同时记录一条回滚操作,借助undo log和记录的最新值,我们可以得到该记录的之前所处的状态。这也是MySQL中的多版本并发控制MVCC的基础,在可重复读RR的隔离级别中就需要用到。

直到不再有事务需要用到,即没有比这个undo log更早的事务视图时,undo log才会删除。而长事务意味着数据库中会存在一个很老的事务视图,所有它可能用到的undo log都要保留,这些undo log会占据大量空间。

当事务因为某种原因需要回滚时,就需要使用undo log来恢复。

你可能感兴趣的:(数据库,mysql,数据库,database)