redo log

WAL 预写日志技术
所有的数据库都有这个技术

这个技术解决什么问题?为啥要这个技术
当我们直接把数据写到磁盘上的时候, 这个数据是不同行不同的列, 在磁盘上是不同的区域.
比如

  1. update T set c=c+1 where id>2;

那会让磁头先寻找位置, 然后更改数据, 中间产生大量的随机io, 很慢!
可不可以写把这次改动先记录下来, 然后异步的刷到磁盘上面, 可以的, 就是WAL, 把随机io改为顺序io

那数据写记录下来, 没真正的写到数据对应的磁盘那个区域, 那我读数据怎么读取呢?
数据从磁盘读取的时候, 会和WAL里面的数据进行merge的

两阶段提交

讲组复制之前必须要了解双1机制
比如一个update语句在mysql内部持久化的流程是这样的

update T set c=c+1 where id=2;

执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程:

  1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

image.png
数据在写入binlog之前要写入redo log, 为啥要redo log这个. 能不能去掉他, 直接写入binlog?
不行, 为啥不行

  1. 当mysql异常崩溃的时候, 只有binlog无法恢复, 因为binlog代表的是一个语句, 这个语句的更新设计多行, 多列, 在磁盘上是多个块. 当mysql在写磁盘某一个块的时候崩溃了, 需要redo log去恢复, binlog只是记录的sql不知道mysql写数据块写哪里了.
  2. 数据持久化到磁盘, 需要redo log帮忙提升性能, 把随机io改为顺序io

那这个redo log这么好, 我可不可以不要binlog, 只要redo log?
不行,

  1. redo log没有归档的功能
  2. 只有innodb有
  3. 版本不一样的redo log差异很大
  4. redolog大小有限制, 超过就刷盘了.

那这个两阶段为啥要这个顺序?可不可以先写redo log再写binlog?

  1. 先写redo log再写binlog的问题

假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。然后会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。 因为主库上的binlog恢复的数据应该和主库上的现有数据保持一致

  1. 先写binlog再写redo log

如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

mysql之前的复制方式

mysql的binlog 一条条记录着sql/row/mixed

这些信息, mysql5.6之前是单线程的一条条的在从库回放, 那能不能异步 并行的回放?

协调者把不同的sql发给不同的线程处理
image.png
如果随意的异步并行把sql分给多线程, 会出现这样的问题

  1. # 主库是这样的binlog (假设是sql格式, 方便理解)
  2. 1. update T set c=c+1 where id>2;
  3. 2. update T set c=3 where id>10;
  4. # 从库, 多线程回放
  5. 线程1: 正在执行一堆任务, 此时这个sql进入了队列: update T set c=c+1 where id>2;
  6. 线程2: 正在执行一堆任务, 此时这个sql进入了队列: update T set c=3 where id>10;

如果从库上线程2的执行比线程1快, 那sql的顺序就反了, 业务的正确性就受到了影响

那么单线程会导致什么问题?
mysql的从库大大落后主库, 导致主从延迟过大, 如果主从延迟过大的话, 从库的实际作用几乎没有了.
比如: 很多数据要求的延迟大概就是几秒或者2分钟, 再慢就查主库了.

如何提升mysql复制的能力?
基于组的并行复制, 组提交的并行复制

组的复制方式

  1. 能够在同一组里提交的事务,一定不会修改同一行;
  2. 主库上可以并行执行的事务,备库上也一定是可以并行执行的。

具体的实现是:

  1. 在一组里面一起提交的事务,有一个相同的 commit_id,下一组就是 commit_id+1;
  2. commit_id 直接写到 binlog 里面;
  3. 传到备库应用的时候,相同 commit_id 的事务分发到多个 worker 执行;
  4. 这一组全部执行完成后,coordinator 再去取下一批。

这个策略,目标是模拟主库的并行模式

但是,这个策略有一个问题,它并没有实现“真正的模拟主库并发度”这个目标。在主库上,一组事务在 commit 的时候,下一组事务是同时处于“执行中”状态的。
这个方案很容易被大事务拖后腿, 这段时间,只有一个 worker 线程在工作,是对资源的浪费。

利用mysql的两阶段提交实现这个
能够到达 redo log prepare 阶段,就表示事务已经通过锁冲突的检验了

MySQL 5.7 并行复制策略的思想是:

  1. 同时处于 prepare 状态的事务,在备库执行时是可以并行的;
  2. 处于 prepare 状态的事务,与处于 commit 状态的事务之间,在备库执行时也是可以并行的

binlog 的组提交的时候,有两个参数:

  1. binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync
  2. binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync

这两个参数是用于故意拉长 binlog 从 write 到 fsync 的时间,以此减少 binlog 的写盘次数。在 MySQL 5.7 的并行复制策略里,它们可以用来制造更多的“同时处于 prepare 阶段的事务”。这样就增加了备库复制的并行度

这两个参数,既可以“故意”让主库提交得慢些,又可以让备库执行得快些

双1机制

sync binlog和redo log的参数配置

那么组提交了, 那把binlog和redo log刷盘是不是也可以做成组提交强刷磁盘?
刷盘有哪些参数

  1. 写pagecache等os刷盘
  2. 写pagecache, 每秒刷1次盘
  3. 不写pagecache, 强刷磁盘

有了组提交, 强刷磁盘性能就不那么差了, 因为是批量写, 而且redo和binlog都是顺序io写

附录

mysql存储

image.png


image.png
表空间是.ibd文件
page就是B+树上的page
Trx id是事务id, 表示是那个事务更新的
Coln是行数据


数据段: B+树的叶子节点
索引段: B+树的非叶子节点
innodb中, 段由innodb自己管理的