MySQL三大日志

redo log 、undo log 、binlog、两阶段提交

常见的 mysql 日志有二进制日志 binlog(归档日志)和重做日志 redo log(事务日志)和 undo log(回滚日志)。

undo log 回滚日志

undo log 保证了事务的原子性,undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。
每当 innoDB 引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到 undo log 里,比如:

  • 在插入一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录删掉就好了;
  • 在删除一条记录时,要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了;
  • 在更新一条记录时,要把被更新的列的旧值记下来,这祥样之后回滚时再把这些列更新为旧值就好了。

ReadView+undo log 实现 MVCC

在读已提交和可重复读的隔离级别,普通读是通过 ReadView+undo log 实现的

Buffer Pool

MySQL 的数据都是存在磁盘中的,那么更新一条记录的时候,得先要从磁盘读取该
记录,然后在内存中修改这条记录。那修改完这条记录是选择直接写回到磁盘,还是选择缓存起来呢?
当然是缓存起来好,这样下次有查询语句命中了这条记录,直接读取缓存中的记录,就不需要从磁盘获取数据了。为此,Innodb 存储引擎设计了一个 Buffer Pool,来提高数据库的读写性能。当修改数据时,如果数据存在于 Buffer Pool 中,那直接修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致),为了减少磁盘 IO,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。

redo log 重做日志

MySQL 中数据是以页为单位,查询一条记录会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。

后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。

更新表数据的时候,也是先更新Buffer Pool 的数据,如果没有则先把数据读到Buffer Pool然后会把“在某个数据页上做了什么修改”记录到redo log buffer,接着刷盘到 redo log 文件里。

那么什么时候会进行刷盘呢?别急,往后看!

为什么需要 redo log

为了防止 buffer pool 的脏页丢失而设计。buffer pool 基于内存,提高了 mysql 性能,但是内存的数据没有持久化到磁盘,mysql 宕机后会数据丢失,为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会**先更新内存并标记为脏页,然后将本次对页的修改以 redo log 的形式记录下
来,**这个时候更新就算完成了。
后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘
里,这就是 WAL(Write-Ahead Logging)技术。WAL 技术指的是,MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。

刷盘时机

  • 事务提交:当事务提交时,log buffer 里的 redo log 会被刷新到磁盘(可以通过innodb_flush_log_at_trx_commit参数控制,后文会提到)。
  • log buffer 空间不足时:log buffer 中缓存的 redo log 已经占满了 log buffer 总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
  • Checkpoint(检查点):InnoDB 定期会执行检查点操作,将内存中的脏数据(已修改但尚未写入磁盘的数据)刷新到磁盘,并且会将相应的重做日志一同刷新,以确保数据的一致性。
  • 后台刷新线程:InnoDB 启动了一个后台线程,负责周期性(每隔 1 秒)地将脏页(已修改但尚未写入磁盘的数据页)fsync 刷新到磁盘,并将相关的重做日志一同刷新。
  • 正常关闭服务器:MySQL 关闭的时候,redo log 都会刷入到磁盘里去。

redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?

写入 redo log 的方式使用了追加操作,是顺序写,而磁盘写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写。「顺序写」比「随机写」高效的多,因此 redo log 写入磁盘的开销更小。

修改数据页后(脏页)为何不直接刷盘,还需要 redo log

数据页大小是 16kb,刷盘(随机写)要进行 io 比较耗时,如果仅修改了数据页几 byte 的数据,没必要刷盘。而写 red log  一行记录(顺序写)可能就占几十  Byte,相对来说更优。

redolog 采用循环写,边写边擦除日志,只记录未被刷入磁盘的数据 ,已刷入磁盘的数据会从 redolog 中擦除,而 binlog 保存的全量日志,如果整个数据库都删除了,可以用 binlog 恢复数据。

Buffer Pool 的脏页刷新到了磁盘中,那么 redo log 对应的记录也就没用了,这时候擦除这些旧记录,以腾出空间记录新的更新操作。

bin log 归档日志

redo log 是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。

binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server  层,会记录所有涉及更新数据的逻辑操作,并且是顺序写

binlog 日志有三种格式,由 binlog_format 指定:

  • statement
    记录 SQL 原文,这样同步数据时仅需执行 SQL 即可,但是如果 SQL 中有 now(),那么此时同步获取的时间和原库的时间是不一致的。因此在同步数据时,需要指定为 row 格式,不仅记录 SQL,还包含具体数据,即把 now()变成具体时间戳
  • row
    记录 SQL 和具体数据,因此这种格式会占用更多的空间,恢复与同步时会更消耗 IO 资源,影响执行速度。
  • mixed
    前两者混合

bin log 刷盘时机  

事务执行过程中,先把日志 write 到binlog cache,事务提交的时,再把binlog cache fsync到binlog 文件中。

因为一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个binlog cache

可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。

两阶段提交

在执行更新语句过程,会记录 redo log 与 binlog 两块日志,以基本的事务为单位,redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以 redo log 与 binlog 的写入时机不一样。
如果在写完 redolog 日志后,binlog 日志写期间发生异常,就会导致数据不一致。对此 innodb 采用两阶段提交方案(XA 事务),把 redo log 写入拆成 prepare 和 commit 两阶段

这样 binlog 写期间发生异常,redolog 还处于 prepare 阶段,回滚事务。
而如果 binlog 期间无异常,commit 期间异常,就不会回滚事务,因为 binlog 日志已经记录成功,mysql 就认为是完整的,会提交事务恢复数据。

异常重启出现什么现象

A、B 两个时刻,redo log 都处于 prepare 阶段,mysql 重启后会扫描 redo log,碰到处于 prepare 阶段的 redo log,就会拿着他的事务 id 去 bin log 查是否存在此 id,若不存在,则说明 binlog 还没有刷盘,回滚事务,若存在,则说明 binlog 已经刷盘,提交事务。如此便可保证两个日志的一致性。

事务没提交的时候,redo log 会被持久化到磁盘吗?

总结

MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性,这两个都是存储引擎层面的。

MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性,是 Server 层面的,所有存储引擎都可以使用。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计