前言
一条数据在更新过程当中,如果中途 mysql crash 了,mysql 是如何保证数据的一致性和持久性的?在这个过程中 mysql 的日志系统起到了至关重要的作用。本文将会介绍 mysql 中的 undo log、redo log 和 bin log 在这其中的作用。
buffer pool
在数据更新的时候,数据并不是实时同步到硬盘中,而是在一块缓存 buffer pool 中更新,如果缓存中没有查询到该数据,则从磁盘中加载到 buffer pool 中。
当然,缓存的作用是为了提高 IO 性能,可以通过将数据先保留在缓存中,然后在适当的时机,批量写入到硬盘中。
并且在查询数据时,先是从缓存中进行查询,不用去磁盘中查找,减少 IO 的操作,加快查询的速度。
undo log
我们知道 InnoDB 是支持事务的,在事务提交失败时,是会回滚到执行之前的状态,那么肯定是需要保存之前的状态才可以进行恢复的,这个就是通过 undo log 来实现的。
在数据写入 buffer pool 的同时会将更新前的数据保存在 undo log 中,通过该日志语句便可以在事务回滚时,恢复到之前的状态。
redo log
redo log 的作用
再回到 buffer pool ,因为它是缓存,是在内存中,所有它的缺点也显而易见,那就是当服务器宕机中,缓存中的数据会丢失,那么 mysql 是如何保证数据的持久性呢?这个时候就要来介绍介绍 redo log 了。
在数据更新到 buffer pool 后,这个时候会将更新后的数据记录到 redo log buffer 中,这个也是一个缓存区,它当然也具备了缓存优缺点,并且默认是在提交事务的时候写入到 redo log 中,刷盘的策略可以根据 innodb_flush_log_at_trx_commit
来设置
- 0,不刷入磁盘
- 1,立即刷入磁盘(默认)
- 2,先刷入到 os cache 中
因为 redo log 是顺序写入,所以 IO 性能不会太差。
当 buffer pool 中的数据还没有写入到磁盘中时,发生了宕机,当 mysql 重启时,会读取已经持久化 redo log 中的数据,再恢复到 buffer pool 中。
在开启事务准备更新一条记录时,InnoDB 会先在 buffer pool 中更新数据,然后将更新后的数据记录到 redo log buffer
中,这也是一个缓存。当然这个时候也是会发生宕机,但是没关系,如果该部分数据丢失,则认为该次事务提交失败,数据会恢复到之前的状态。
redo log文件结构
redolog 是由多个固定大小的文件组成的一个环形结构,并在这个环形结构中不断的写入与覆盖的过程。
- write pos:记录当前的位置
- checkpoint:当前要擦除的位置
当有新的 redo log 写入时,从 wirte pos 位置往后写,而 check point 是上一次已经刷入磁盘的数据的位置,也是要不断的往后推进,然后将数据刷入磁盘中。
binlog
是在 mysql 层级记录的日志,主要是用于主从复制和数据恢复,可以通过某个时间的全量备份+binlog 来恢复到任意时间内的状态。
和 redo log 的区别
性质 | redo log | bin log | |
---|---|---|---|
实现 | innodb 独有实现 | mysql server 层级实现,所有的引擎都可以使用 | |
内容 | 物理 log, 记录的是“在某个数据页上做了什么修改” | 逻辑 log,给 ID=2 这一行的 c 字段加 1 | |
写入 | 循环写入 | 追加写,写到一定大小切换下一个文件继续写 | |
应用 | 崩溃恢复(crash-safe) | 主从同步,数据恢复 |
两阶段提交
为什么需要两阶段提交?
是为了让 redo log 和 bin log 保持逻辑一致性。
- 如果先写 redolog 后写 bin log。假设 redo log 写完,写 bin log 时 crash 了。
因为 redo log 写完了,所以即使系统崩溃,也可以恢复数据,但是 bin log 没写完 crash 了,这个时候 bin log 中少了该条语句,因此数据备份的时候,如果使用了该 bin log 则会少一次更新。
- 如果先写 bin log 后写 redo log。假设 bin log 写完,写 redo log 时 crash 了。
因为 redo log 没写完,所以该事务没有生效,但是 binlog 中已经有该条记录,所以使用 bin log 时,会多出一个事务,与原来的数据不一致。
所以使用两阶段提交可以解决上面两种场景。
两阶段提交的实现逻辑
- 在更新数据时,会先在 redo log 中记录当前更新的数据,并且标记为 prepare 状态
- binlog 再进行写入
- 事务提交时, redo log 再将该条记录标记为 commit 状态并且刷入到磁盘中。
通过 prepare 和 commit 两种状态来完成两阶段的提交实现。
验证两阶段提交
- 如果在两阶段提交的第一步后发生 crash,也就是 redo log 已经更新了数据并且为 Prepare 状态,但是 binlog 还未写入就出现了 crash,这个时候,mysql 重启后,因为 redo log 未 commit,可以通过回滚将数据恢复。
- 如果在第二步发生 crash,也就是 redo log 为 prepare 状态,并且 binlog 已经写入,但是这时候出现了 crash,在 mysql 重启后,因为 binlog 已经有了记录,所以会继续提交该事务,否则 bin log 中数据新增了一条,而 redo log 没提交则可能发生两者数据不一致的情况。
相关链接
- 一条 SQL 的执行过程详解
- 基于Redo Log和Undo Log的MySQL崩溃恢复流程
欢迎关注,互相学习,共同进步~
我的个人博客
公众号:编程黑洞