一、redolog
redo log 是 InnoDB 存储引擎特有的日志,用于记录数据页的物理修改,保证事务的持久性和原子性。redo log 是循环写入的,由两部分组成:一块固定大小的内存区域(redo log buffer)和一组固定大小的磁盘文件(redo log file)。当事务对数据进行修改时,会先将修改记录到 redo log buffer 中,然后在适当的时机将其刷新到 redo log file 中。这样即使数据库发生异常重启,也可以根据 redo log 恢复数据。
1. WAL技术
write-Ahead Logging(日志先行),先写日志,再写磁盘。
当有一条记录需要更新时,InnoDB引擎会先把记录写到redo log里,并更新内存。
2. LSN
如果脏页没刷完,数据库宕机了,那么必然是需要使用 redo log 来恢复数据的。那么 redo log 应该从哪开始恢复数据呢?为解决这个问题 InnoDB 为 redo log 记录了序列号,这被称为 LSN(Log Sequence Number),可以理解为偏移量,越新的日志 LSN 越大。InnoDB 用检查点(checkpoint_lsn
)指示未被刷脏页的 redo log 数据从这里开始,用 lsn
指示下一个应该被写入日志的位置。不过由于有 redo log buffer 的缘故,实际被写入磁盘的位置往往比 lsn
要小。
crash-safe
:有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
二、binlog
redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
用于记录 SQL 语句的逻辑修改,保证事务的一致性。binlog 是追加写入的,由一个 binlog 文件序列和一个索引文件组成。当事务提交时,会将 SQL 语句记录到 binlog 中。binlog 主要用于数据备份、恢复和主从复制。
为什么会有两份日志呢?
因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。
1. binlog&redolog
- redolog是innodb引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
- redolog是物理日志,记录的是”在某个数据页上做了什么修改“;binlog是逻辑日志,记录的是这个语句的原始逻辑。比如”给ID=2这一行的c字段加1“。
- redolog是循环写的,空间固定会用完;binlog是可以追加写入的。”追加写“是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
三、事务的两阶段提交
https://zhuanlan.zhihu.com/p/552706911?utm_medium=referral
1. 为什么
为什么必须有“两阶段提交”呢?
这是为了让两份日志之间的逻辑一致。
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。
- 有 binlog 为什么还要 redo log ?
- binlog 不知道数据库究竟是在哪一时刻丢失了哪部分数据,只能从备份点开始对 binlog 记录重放来恢复数据,比较耗时。
- binlog 恢复是需要我们手动执行的,而 redo log 可以在服务器重启后自动恢复数据。
- WAL + 先写缓冲 + 异步刷脏页有效提升了磁盘的 IO 效率。
- 有 redo log 为什么还要 binlog?
- binlog 是服务器层面的功能,redo log 是 innoDB 的功能。redo log 帮助 InnoDB 实现了性能提升、自动恢复。但其他存储引擎是无法使用 redo log 的能力的。
- 我们也可以关闭 binlog,但大多数情况下我们都会开启,因为开启的好处更多。比如,主从模式需要订阅 binlog 进行主从复制,以及可以通过 binlog 进行数据库的增量备份和恢复。
2. 场景
某天14:00发现中午12:00有一次误删表,需要找回数据
- 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
- 然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
- 把表数据从临时库取出来,按需要恢复到线上库去。
3. 流程
- 服务器收到事务开始的指令,为事务生成一个全局唯一的事务 id。这个事务 id 在记录 binlog 和 redo log 时都会使用。
- 如果缓存池中没有 no=1 所在数据页的数据,从磁盘中找到对应的数据页(注意,这里是一个数据页,不是一条记录),把数据页加载到缓存。
- 修改缓存数据页中 no=1 的数据。
- 记录数据到 redo log buffer[^4]、binlog cache[^2]。根据 redo log 刷盘的策略,这个过程中 redo log buffer 可能会被刷新到磁盘。
- 服务器收到事务提交的指令。
- 刷新 redo log buffer 到磁盘,并标记该事务的状态为 prepare。此操作称为 redo log prepare。
- 刷新 binlog cache 到磁盘。
- 刷新 redo log buffer 到磁盘,并标记该事务的状态为 commit。此操作称为 redo log commit。
- 向客户端返回事务执行的结果。
在宕机后,重启 MySQL 时,InnoDB 会自动恢复 redo log 中
checkpoint_lsn
后的,且处于 commit 状态的事务。如果 redo log 中事务的状态为 prepare,则需要先查看 binlog 中该事务是否存在,是的话就恢复,否则就回滚(通过 undo log 回滚。脏页一直在刷,更新了脏页,但事务没提交就宕机了,所以需要回滚)。
4. 异常处理
如果在MYSQL重启之后会按照顺序读取redo log 文件,当碰到处于 prepare阶段的redo log 文件,就会拿着redo log 的XID去binlog中寻找,看是否有该XID:
-
如果binlog中没有该XID,那么就说明redo log 刷盘完成,但binlog还没有,则回滚事务,即时刻A阶段异常
-
如果binlog中有该XID,那么就说明redo log 和binlog都刷盘完成,但commit标识还没有提交,则提交事务,即时刻B阶段异常
所以从上面可以看出,对于处于 prepare 阶段的 redo log,即可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证 redo log 和 binlog 这两份日志的一致性了。
总结:两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。
问题1:如果事务还没有提交,redo log会持久化吗?
答案:会持久化。因为事务执行过程中redo log 会直接写到 redo log buffer中,这些在 redo log buffer里的redo log 会每隔一秒就被持久化到磁盘中(根据持久化策略决定)。所以事务没有提交,redo log可能会持久化到磁盘的。
问题2: 如果在事务还没有提交,而redo log 已经被持久化磁盘了。这时MYSQL崩溃了,怎么办?
答案:在MYSQL重启时,事务会进行回滚操作。因为事务没提交的时候,binlog 是还没持久化到磁盘的。
5. 性能影响
两阶段提交解决两个日志一致性,那它会不会带来新的问题呢?
虽然两阶段提交是解决了两个日志数据一致性问题,但是它也带来了一定性能问题:
-
磁盘 I/O 次数高:对于“双1”配置(sync_binlog 和 innodb_flush_log_at_trx_commit 都配置为 1),每个事务提交都会进行两次 fsync(刷盘),一次是 redo log 刷盘,另一次是 binlog 刷盘
-
锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。
磁盘 I/O 次数高
因为redo log 和binlog 都是存在对应的缓存里,即redo log缓存在redo log buffer,binlog缓存在 binlog cache中,而持久化它们是各自通过参数来控制的。一般为了数据不会丢失,都会设置这两个参数为1:
-
当 sync_binlog = 1 的时候,表示每次提交事务都会将 binlog cache 里的 binlog 直接持久到磁
-
当 innodb_flush_log_at_trx_commit = 1 时,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘
-
这两个参数都设置为1,就是“双1”配置。那么当有事务提交的时候,至少就是刷两个磁盘(分别是 redo log 刷盘和binlog 刷盘),所以就导致了性能问题。