MySQL 日志原理
日志
mysql当中的日志一共用三种:
- undo log : 回滚日志,由Innodb存储引擎实现。用于 事务回滚和MVCC,实现了事务当中的 原子性。
- redo log : 重做日志,由Innodb存储引擎实现,用于掉电等情况下的 故障恢复,实现了事务当中的 持久性。
- binlog : 归档日志,是 Server层 生成的日志,用于 数据备份和主从复制。
事务当中的 隔离性 是通过 MVCC多版本控制链 实现的。
接下来我们会了解三种日志是怎么工作的。
Undo Log
为了实现事务的回滚,即撤回到事务之前的状态,Innodb提出了 undo log。
每当IDB于一条记录进行操作(增删改)时,会把回滚(撤回操作)需要的信息记录下来。
- 对于插入,需要记下新记录的主键,回滚时删除。
- 对于删除,需要记下记录的内容,回滚时重新插入。
- 对于更新,需要记下旧值,回滚时改回去。
不同操作的undo log的格式不尽相同。以更新为例,undo log除了记录本身之外,还包括了修改事务id trx_id
和指针 roll_pointer
指向上一个版本,形成版本链。
也是因为上面的原因,undo log除了实现事务回滚之外,也可以用来实现 MVCC。
对于 读提交 和 可重复读 隔离级别的事务来说,其 快照读(普通Select) 是通过Read View + undo log来实现的。
四种隔离级别:读未提交、读提交、可重复读、串行化。后三个分别解决了 脏读、不可重复读、幻读 的问题。
- 读提交 在每次select语句之前都创建了一个快照Read View,可以解决脏读,但是无法重复读。
- 可重复读 是在事务开始的时候创建了一个Read View,这样重复读同一个Read View,结果不变。
MVCC通过Read View + undo log实现,undo log为每条记录保持多份历史数据,mysql在快照读时,会根据RV里面的信息,顺着UL的版本链找到可见的记录。
在事务提交后,undo log不会直接清除,因为部分版本链可能还有用。但是会被逐步清除(delete_bit)。
Redo Log
Undo Log保存了事务之前的数据库状态,而 redo log 则保存了事务 完成之后 的状态。
redo log是针对 页 做的操作记录,也正因此,即使事务在数据页写磁盘的时候出现了故障,重启之后,尽管内存当中的脏页丢失了,mysql依然可以根据保存好的redo log来恢复其应该有的状态。所以说,redo log实现了事务的 持久性。
- undo log存放在undo页当中。undo页与数据页一样,存放在Innodb向系统申请的一段内存空间当中,叫做Buffer Pool,等待持久化到磁盘。
- redo log存放在 Redo log buffer。
- Buffer pool当中的页不会立即写入磁盘,而是先写日志,等待合适的时机写入磁盘。这叫做 WAL(Write-Ahead Logging)技术。
可以看到,undo log的持久化依赖于redo log。那么redo log是怎么持久化的呢?
redo log由于在磁盘上顺序追加写,所以速度会比普通的页持久化更快。
redo log 可能会在以下时机写入磁盘redo log文件:
- mysql关闭
- 每隔1s
- redo log buffer过半
- 事务提交时
redo log文件和redo log buffer大小都是固定有限的。从实际的角度来看,一旦事务提交,redo log就没有意义了,可以清除。
因此,redo log采用了一种循环写的策略。
如上图所示,write_pos和checkpoint都顺时针移动。wrtie_pos表示写入的位置,checkpoint表示要擦除的位置。
因此,图中红色部分表示空白可写区域,而蓝色部分表示待写脏页。一旦 write_pos == checkpoint
,则表示没有可写区域,此时mysql阻塞,将脏页写入磁盘,然后清空此部分redo log。
注意,上面讲的是 脏页 写磁盘的过程,与 redo log 写磁盘的时机区分开。
脏页写磁盘还有一些其他的时机。redo log buffer满只是其中一种。