持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
为什么需要 Redo Log?
如果没有 Redo Log, 要想保证 MySQL 宕机后尽可能不丢失数据,就要每个事务都直接写到磁盘中,但是一个事务往往需要修改多张表的多条记录,多条记录又很可能分布在不同的 Page 中,对应到磁盘的不同位置,所以一次事务的提交往往要多次磁盘的随机 I/O,性能很差
为了解决这个问题,就有了 WAL 技术,也就是 Write-Ahead-Log 思路:先在内存中提交事务,然后再写日志,然后后台任务吧内存中的数据异步刷到磁盘中。日志是顺序地在尾部 append 的,所以能避免一个事务发生多次磁盘随机 I/O
WAL 技术类似可以理解为客栈做生意,老板有一个粉板,专门记录客人的赊账,做生意的时候,遇到顾客赊账了,就记在粉板,等打烊以后再把账本翻出来核算,如果有时候生意特别好,粉板写不下了,就停下来先把账本翻出来核算,核算完把粉板相应的部分擦除。其中 Redo Log 就是粉板
如何使用 redo 日志进行数据恢复
日志类型
- 作用于 Page 的 REDO
- 作用于 Space 的 REDO
- 提供额外信息的 Logic REDO
逻辑和物理结构
从逻辑上讲,Redo Log 是一个无限延长的字节流,从数据库安装好并启动的时间点开始,日志便源源不断地追加,永不结束
从物理上讲,日志不可能是一个永不结束的字节流,所以 Redo Log 其实是一个固定大小的文件,写到尾部之后,回到头部覆写。之所以能覆写,是因为一旦 Page 数据刷到磁盘上,日志数据就没有存在的必要了
磁盘是按块读取的,所以为了保证磁盘的 I/O 效率,都是整块地读取和写入。对于 Redo Log 来说,每个 Redo Log Block 都是 512 字节(因为早期的磁盘一个扇区就是存储 512 字节数据)
为了通过物理结构实现逻辑上的无限延长,使用了 LSN,也就是逻辑上日志按照时间顺序从小到大的编号。在 InnoDB 中,LSN 是一个 64 位的整数,取的是从数据库安装启动开始到当前所写入的总代日志字节数。因为事务有大有小,每个事务产生的日志数量是不一样的,所以日志是变长日志,因此 LSN 是单调递增的
物理层面,是一个固定的文件大小,每 512 字节是一个 Block,循环使用。因此很容易通过 LSN 换算出所属的 Block
写入流程
事务并发修改数据,产生 Redo Log 后把日志写到 log buffer中,然后后台 log_write 会将日志写到系统缓存中,然后 log_flusher 会把缓存中的日志写到磁盘中,写完后修改就不会丢了,最后通知用户线程完成了,在这之前用户线程是阻塞的,由此可以看出 redo 日志的持久化会影响 MySQL 性能
Checkpoint 机制
由于前面介绍的,Redo Log 是物理上是有限空间的,为了保证 redo Log 写满后还能被继续使用,也为了清理 redo Log 来保证磁盘可用性,故障恢复也更快一些,所以有了 checkpoint 机制
什么时候出发 checkpoint?
- u master thread checkpoint
- u flush_lru_list checkpoint
- u async/sync flush checkpoint
- u dirty page too much checkpoint
数据库恢复流程
- 读取 checkpoint 信息
- 从 checkpoint 位置开始读取剩余日志
- 解析日志并按 space_no 与 page_id 构建 hash 表
- 应用 REDO 日志,RDDO日志回放保证幂等性
- 解析 binlog 构建 xid 列表
- 扫描回滚段构建待提交事务列表
- 回滚掉未在 xid 列表中的事务
区别
提升 Redo Log 写入效率
并发写入
给 log buffer 中加锁;MTR 会计算长度,然后申请这个空间,这是原子的,MTR 将自己的 redo 日志拷贝到 log buffer,可能会出现后申请的先拷贝,为来解决这个问题,引入了一个数据结构,log buf,一个 MTR 拷贝后就把对应位置写上拷贝的大小,这时候另一个拷贝的时候,扫描往后推相应的字节然后填入
Redo Log 落盘方式
使用 innodb_flush_log_at_trx_commit 作用于事务提交:
- 当设置该值为 1 时,每次事务提交都要做一次 fsync,这是最安全的配置,即使宕机也不会丢失事务;
- 当设置为 2 时,则在事务提交时只做 write 操作,只保证写到系统的 page cache,因此实例 crash 不会丢失事务,但宕机则可能丢失事务
- 当设置为 0 时,事务提交不会触发 REDO 写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失 1 秒钟内的事务。
改造
Redo 和 binlog 如何保证一致?
不一致主库和从库就不一致了
Mysql 通过两阶段提交保证一致性
Prepare 之前宕机了,两个日志一致的
Write Binlog 之前宕机了,事务在 redo中 prepare,查binlog 发现没有,就把redo 事务回滚了,一致了
Sync bin之后,redo prepare,bin 存在,就把事务提交,bin 和 redo 都一致
Commit 宕机他们俩还是一致的
但是两阶段提交比较耗时,将 binlog 写成特殊类型的redo 也写到 redo 中,放到同一个 MTR ,保证原子性,他们就同时落盘了。bin log 写到内存中就行, 只有 redo 需要落盘,两次 sync 变成一次 sync,这时候假设 ndb 宕机,就会丢一部分 bin,这时候扫 redo 日志,把缺失的扫回来就行