MySQL的crash-safe原理
一、前言
MySQL保证数据不会丢失的能力
- 能够恢复到任何时间点的状态(binlog);
- 能够保证MySQL在任何时间点突然崩溃,再重启后之前提交的数据都不会丢失(crash-safe);
crash-safe的能力就是保证第二点,从MySQL整体来看,总共分为两层(server层、引擎层),binlog日志属于server层,只能用于归档,不能保证crash-safe。在InnoDB存储引擎中,事务提交过程中中任何阶段,MySQL突然崩溃,重启后都能保证事务的完整性,已提交的数据不会丢失,未提交的数据会自动进行回滚。这个主要依赖的就是redo log和undo log这两个日志。
两种日志的不同点主要有三点:
- redo log 是InnoDB存储引擎特有的,binlog是MySQL的server层实现的,所有的引擎都可以使用;
- rodo log是物理日志,记录的是“在某个数据页上做了什么修改”。binlog是逻辑日志,记录的是这条语句的原始逻辑;
- rodo log是循环写的,空间固定,会用完,binlog是可以追加的(binlog文件写到一定大小后会切换下一个,并不会覆盖以前的日志);
一条update语句执行内部流程:
update T set money=money+10000 where ID = 1
- 执行器先找存储引擎取D=1这一行,ID是主键,引擎直接找到这一行数据,如果 ID=1 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回;
- 执行器拿到引擎给的行数据,把这个值加上 10000,得到新的一行数据,再调用引擎接口写入这行新数据;
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态,然后告知执行器执行完成了,随时可以提交事务;
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘;
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交commit状态,更新完成。
两阶段提交
将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交",这是为了让两份日志之间的逻辑一致。
二、WAL机制
MySQL更改数据的时候,不直接写磁盘文件中的数据,最主要就是性能问题。因为直接写磁盘文件是随机写,开销大性能低,没办法满足MySQL的性能要求。所以才会设计成先在内存中对数据进行更改,再异步写入磁盘。但是内存中的数据并不能持久化,在崩溃、重启等事故后,内存数据就会丢失,所以还需要加上写日志这个步骤,保证MySQL崩溃、重启后,还能通过日志中的记录进行事务操作。
三、核心日志模块
更新SQL执行过程中,总共涉及MySQL日志模块其中的三个核心日志,分别是redo log(重做日志)、undo log(回滚日志)、binlog(归档日志)。
1、重做日志 redo log
redo log也称为事务日志,由InnoDB存储引擎层产生。记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置,因为修改会覆盖之前的)。
redo log就是WAL的典型应用,MySQL在有事务提交对数据进行更改时,只会在内存中修改对应的数据页和记录redo log日志,完成后即表示事务提交成功,至于磁盘数据文件的更新则由后台线程异步处理。由于redo log的加入,保证了MySQL数据一致性和持久性(即使数据刷盘之前MySQL奔溃了,重启后仍然能通过redo log里的更改记录进行重放,重新刷盘),此外还能提升语句的执行性能(写redo log是顺序写,相比于更新数据文件的随机写,日志的写入开销更小,能显著提升语句的执行性能,提高并发量);
2、回滚日志 undo log
undo log,主要就是提供了回滚的作用,但其还有另一个主要作用,就是多个行版本控制(MVCC),保证事务的原子性。在数据修改的流程中,会记录一条与当前操作相反的逻辑日志到undo log中(可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录),如果因为某些原因导致事务异常失败了,可以借助该undo log进行回滚,保证事务的完整性。
3、归档日志 binlog
binlog在MySQL的server层,不属于任何引擎,主要记录用户对数据库操作的SQL语句(除了查询语句)。之所以将binlog称为归档日志,是因为binlog不会像redo log一样擦掉之前的记录循环写,而是一直记录(超过有效期才会被清理),如果超过单日志的最大值(默认1G,可以通过变量 max_binlog_size 设置),则会新起一个文件继续记录。但由于日志可能是基于事务来记录的(如InnoDB表类型),而事务是绝对不可能也不应该跨文件记录的,如果正好binlog日志文件达到了最大值但事务还没有提交则不会切换新的文件记录,而是继续增大日志,所以 max_binlog_size 指定的值和实际的binlog日志大小不一定相等。由于binlog有归档的作用,所以binlog主要用作主从同步和数据库基于时间点的还原。