我们知道事务有四大特性,原子性、一致性、隔离性与持久性。那么MySQL中的事务是如何保证这些特性的呢?

MySQL的存储引擎InnoDB使用重做日志保证一致性与持久性,回滚日志保证原子性,使用各种锁来保证隔离性。

重做日志(redo log)

防止在发生故障的时间点,尚有脏页未写入磁盘,在重启MySQL服务的时候,根据重做日志进行重做,从而达到事务的持久性这一特性。 重做日志由两部分组成,一是内存中的重做日志缓存,其是易失的;二是重做日志文件,其是持久的。重做日志是物理日志,记录的是对于每个页的修改。事务开始后Innodb存储引擎先将重做日志写入缓存(innodb_log_buffer)中。然后会通过以下三种方式将innodb日志缓冲区的日志刷新到磁盘。

  1. Master Thread每秒一次执行刷新Innodb_log_buffer到重做日志文件。
  2. 每个事务提交时会将重做日志刷新到重做日志文件。
  3. 当重做日志缓存可用空间少于一半时,重做日志缓存被刷新到重做日志文件

当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化。为了确保每次日志都写入重做日志文件,在每次将日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作。但这也不是绝对的。用户可以通过修改innodb_flush_log_at_trx_commoit参数来控制重做日志刷新到磁盘的策略。参数默认值为1,表示事务提交时必须调用一次fsync操作。0表示事务提交时,重做日志缓存并不立即写入重做日志文件,而是随着Master Thread的间隔进行fsync操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。 在InnoDB存储引擎中。重做日志都是以512字节进行存储的,称之为重做日志块,与磁盘扇区大小一致,这意味着重做日志的写入可以保证原子性,不需要doublewrite技术。

回滚日志(undo log)

事务发生异常需要回滚,这时就需要回滚日志。回滚日志不同于重做日志,它是逻辑日志,对数据库的修改都逻辑的取消了。当事务回滚时,它实际上做的是与先前相反的工作。对于每个INSERT,InnoDB存储引擎都会完成一个DELETE;对于每个UPDATE,InnoDB存储引擎都会执行一个相反的UPDATE。

事务开始会依照当前版本生成回滚日志,事务也可以使用回滚日志实现多版本并发控制来保证事务的隔离性。事务提交后并不会马上删除回滚日志,因为可能还有其它事务需要通过回滚日志来得到版本信息。所以事务提交时将回滚日志放入链表中,是否可以删除由purge线程判断。

回到刚刚讲的重做日志,未提交的事务和回滚了的事务也会产生重做日志。InnoDB存储引擎会重做所有事务包括未提交的事务和回滚了的事务,然后通过回滚日志回滚那些未提交的事务。使用这种策略需要回滚日志在重做日志之前写入磁盘,使得持久化变得复杂起来。为了降低复杂度,InnoDB存储引擎将回滚日志作数据,记录回滚日志的操作也会记录到重做日志中。这样回滚日志就可以像数据一样缓存起来,而不用在重写日志之前写入磁盘了。

Undo + Redo事务的简化过程

假设有A、B两个数据,值分别为1,2。

  1. 事务开始
  2. 记录A=1到undo log
  3. 修改A=3
  4. 记录A=3到redo log
  5. 记录B=2到undo log
  6. 修改B=4
  7. 记录B=4到redo log
  8. 将redo log写入磁盘
  9. 事务提交

二进制日志(binlog)

在MySQL数据库中还有一种二进制日志,其用来基于时间点的还原及主从复制。从表面上来看其和重做日志非常相似,都是记录了对于数据库操作的日志。但是,从本质上来看有着非常大的不同。 首先重做日志是在InnoDB存储引擎层产生的,而二进制日志是在MySQL数据库的上层产生的。其次,两种日志记录的内容形式不同。二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而重做日志是物理日志,记录的是每个页的修改。此外,两种日志记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行一次写入,重做日志在事务进行时不断地写入。


@COPY

《MySQL技术内幕:InnoDB存储引擎》

MySQL中的重做日志(redo log),回滚日志(undo log),以及二进制日志(binlog)的简单总结

MySQL数据库InnoDB存储引擎Log漫游