本文针对的都是MySQL的Innodb引擎。

1. binlog

二进制日志。记录数据库的所有写操作。注意此处记录的是逻辑日志。

可用于主从复制、增量备份、监听binglog实现缓存一致性。

1.1 常用命令

1
2
3
4
5
6
7
8
-- 查看binlog配置信息
show variables like '%log_bin%';

-- 查看所有的binlog文件
show binary logs;

-- 查看当前正在使用的binlog文件
show master status;

1.2 写入策略

通过show binary logs命令可以看到当前数据库使用的哪些binlog文件, 如下图所示:

mysql会按照一定的规则,生成对应的日志文件,并按照编号从小到大依次生成。

binlog日志在写入时, 按照其日志记录类型不同, 分为如下三种情况:

  • statement: 记录的是sql原文, 每一条对数据的写操作都会记录在binlog中

    • 优点: 不需要记录每一行的变化, 能够减少binlog日志量,提升性能。
    • 缺点:由于记录的是sql原文,所以还需要记录一些额外的相关信息,用来确保在master和slave上执行都是相同的效果。另外有一些函数是无法被复制的, 例如 UUID()
  • row:记录的是每一行的变化

    • 优点:可以清楚的表示每一行的数据变化,所以不需要记录额外信息,同时也无需担心函数执行在不同的节点上会有不同的效果
    • 缺点:会产生大量的日志,特别是那些涉及到整个表的操作,比如增加一个字段,会对该表的所有记录都会产生一条binlog
  • mixed 混合模式。普通操作使用statement,当无法使用statement时,则使用row

2. redo log

重做日志。redo log 是物理日志格式, 存储的是对于每个页的修改的物理情况。

用来保证事务的原子性和持久性

2.1 作用

mysql为了提高效率,对于数据的修改首先都是在内存中进行的,也就是下图中的Buffer Pool,然后按照一定的策略刷新到磁盘中去。

此时就会存在一个问题, 当数据被写入到内存, 但是还没有刷新到磁盘,此时若数据库宕机,那么该数据页上的数据就会丢失。

为了解决这个问题,mysql引入了redo log, 每次对数据页的修改,都会存储到一个redo log日志文件中去,当出现异常宕机后,数据库实例重新启动时,就会去检查这个redo log文件,并对事务进行重放,从而恢复数据。

2.2 写入策略

redo log 也并不是直接写入磁盘的,而是先写入到redo log buffer, 然后按照一定的条件顺序的写入到磁盘中去。

redo log文件路径: 在数据根目录下会有两个文件ib_logfile0ib_logfile1两个文件, 都是用来存储redo log的, 并且写入的时候是轮流写入的。

redo log 分为两部分:

  • redo log buffer 内存中的重做日志缓存, 易丢失
  • redo log file 磁盘中的重做日志文件, 持久化

参数innodb_flush_log_at_trx_commi 控制重做日志redo log buffer刷新到磁盘的策略, 默认值为1, 其取值含义如下:

  • 取值1: 事务提交时必须调用一次fsync操作, 默认值。这种情况不会造成redo log本身的丢失, 假设一种极端情况, 当事务未提交时,数据库宕机,当数据库重新启动的时候, 因为这个事务没有写入到redo log, 所以也没有办法进行恢复, 但这种情况本身也是符号预期的。
  • 取值0:事务提交时不进行fsync操作。那么什么时候将其刷新到磁盘呢? 在master thread中, 每隔1秒就会进行一次fsync操作。这种情况可能会造成这一秒内的数据丢失。
  • 取值2: 事务提交时, 仅将重做日志刷新到文件系统系统缓存, 不进行fsync操作,由文件系统来负责进行刷盘操作。 当服务器发生宕机时,当文件系统的缓存还未刷新到磁盘,也会造成数据的丢失。

综上, 要想保证数据一定不会丢失, 需要将 innodb_flush_log_at_trx_commi设置为1, 其他两种情况都有可能会造成数据的丢失。

此处有一个问题, 既然redo log也要写入文件, 那为啥不直接将buffer pool中的数据直接刷新到磁盘呢?

有两个原因:

  1. redo log 写入的时候是顺序写入的, 而buffer pool中的数据页在磁盘中是随机存储的,所以其写入也是随机写的, 而我们直到磁盘顺序写入和随机写入的性能差距很大
  2. 通常一次数据修改,涉及到的数据很小,此时redo log就会很小,可能只有几个字节。而buffer pool中的每个数据页大小都是16KB, 写入的时候是以数据页为单位进行的,所以最小也是16KB, 相对而言, 写入的数据量更小,性能也会更高

注意,在8.0之后, mysql将AUTO_INCREMENT计数器的变化也写入到重做日志中, 当 MySQL 服务被重启或者处于崩溃恢复时,它可以从持久化的检查点和重做日志中恢复出最新的 AUTO_INCREMENT 计数器,避免出现不单调的主键。

2.3 redo与bin区别

  • 写入时机不同。 binlog只有在事务提交完成后才会写入,所以一个事务中,只会存在一次写。而redo log在事务进行中会不断的写入到重做日志文件中
  • 记录内容不同。 无论binlog按照哪种方式进行存储,其存储的都是对数据的一个变更, 可以理解为我们平时写的一个sql。而redo log记录的则是对于数据页的物理修改
  • 产生地点不同。 redo是在InnoDb存储引擎层产生的,而bin是在数据库上层产生的, 其bin日志所有的存储引擎都会产生

3. undo log

回滚日志。存储的是逻辑方面的日志

可以用来实现事务的原子性

undo log日志存放在数据库内部一个特殊的段中,这个段就是undo段。undo段存储在共享表空间中。

3.1 作用

undo log主要有两个作用:

  • 回滚: 如果由于某种异常需要回滚或者用户主动发起回滚, 此时通过undo log就能实现。 注意, 由于undo log 是逻辑日志,所以在回滚的时候也只是逻辑的恢复数据, 但是数据结构和页本身在回滚之后, 可能和之前的不一定相同。
  • MVVC: 多版本并发控制。在进行快照读的时候,如果记录此时被其他事务占用, 那么此时就直接通过undo log读取之前的行版本信息, 从而非锁定读。

3.2 写入策略

undo log 为了保证更好的并发以及多版本控制,所以其存储不应该因为物理存储变化而变化,所以undo log采用了逻辑存储的方式,来保存这些数据的历史版本。所以在数据库中, 是可能存在一行数据的多个历史版本的, 这些数据在InnoDb看来,和其他的数据没有什么区别,所以,同样的,也会写自己的redo log,通过redo log来保证自己的原子性。

对于undo log, 分为两种:

  • Insert Undo: 对于插入操作,由于其并没有历史版本,所以此处并不需要MVCC,记录undo的目的仅仅是为了回滚
  • Update Undo: 对于更新操作,MVCC需要存储多个历史版本,所以需要存储的信息就比insert类型的多。

3.3 undo与redo区别

  • 在对数据库进行修改时, 不仅会产生redo log, 还会产生一定的undo log
  • redo log是存储在文件里的, 但是undo log是存储在共享表空间一个特殊的段segment内的(undo segment)

参考资料:

  1. 《MySQL技术内幕》
  2. http://mysql.taobao.org/monthly/2015/04/01/