mysql重学(二)二阶段日志

前文

  1. mysql语句执行流程

1.顺序读写和随机读写

我们在前文的更新语句执行流程中介绍了WAL机制,了解到了redolog和binlog是追加机制,这里我们介绍两个概念,算是对上文的补充:

  • 顺序读写:比如写redolog日志是不停的在日志文件末尾追加日志,这是磁盘顺序读写,磁盘顺序读写性能很高
  • 随机读写:比如mysql从磁盘中读取数据页到内存涉及到随机IO访问,性能是比较差的。因为要读取的数据页可能在磁盘任何位置
    对于磁盘随机读来说,主要关注的性能指标是IOPS和响应延迟
    IOPS指标实际上对数据库的crud操作的QPS影响非常大的,因为他在某种程度上几乎决定了你每次能执行多少个SQL语句,底层存储的IOPS越高,你的数据库的并发能力就越高。
    一般对于核心业务的数据库的生产环境机器规划,我们都是推荐使用SSD固态硬盘,而不是机械硬盘,因为SSD固态硬盘的随机读写并发能力和响应延迟要比机械硬盘好得多,可以大幅度提升数据库的QPS和性能。
    更具体了解mysql的日志顺序读写数据随机读写以及linux底层原理

2.两阶段提交

两阶段指redo log的两种状态
redo log的写入分为两阶段,在要更新内存数据时:

  1. 将新数据更新到内存并记录到redo log,redo log
    处于prepare状态并告知执行器可以提交事务,这是第一个阶段
  2. 执行器生成这个操作的binlog并写入磁盘
  3. redo log状态成为提交(commit),更新完成,这是第二个阶段
    两阶段提交保证了两个日志记录一致


    image.png

这里的commit是指事务提交过程中的一个小步骤也是最后一部,当执行完成这个步骤后,事务也就提交完成了,mysql语法中的“commit”包含了图中的commit

redolog和binlog是两个独立的逻辑,采用这种方式(先prepare再commit)能保证日志的一致性。 如何保证一致性呢?假设正则记录一条curd操作,mysqld异常重启,此时,会对binlog的记录和redolog进行检查:

  1. 如果redolog已经commit,则该条记录有效
  2. 如果redolog prepare但未commit,则对binlog的记录再进行讨论:
    2.1 binlog记录成功则 该prepare redolog视为有效
    2.2 binlog记录不成功,则该prepare redolog作为无效,该redolog记录扔掉

binlog写入机制

事务的执行过程中,binlog会先被写入到binlog cache(缓存)中;事务提交时,再将binlog cache写入到binlog文件中。
系统给binlog cache分配一片内存,每个线程一个

binlog_cache用于控制单线程内binlog cache内存大小,当超过这个参数,就要暂存到磁盘

image.png

如上图所示,每个线程(trx )都有自己的binlog cache,
但是只有一份binlog文件

  • write指把日志写入到文件系统的page cache中,并没有写入磁盘所以速度快。
  • fsync为写盘,一般我们认为fsync才占磁盘的IOPS

write和fsync的时机是由参fsync_binlog控制的:

  • =1表示每次提交事务都会执行fsync
  • =0表示只执行write
  • =N(N>1)表示每次都write,但累计N个事务后才fsync
    因此sync_binlog设成一个大值可提升性能

redo log写入机制

事务执行过程中,生成的redo log要先写到redo log buffer中,下面是redo log的三种状态(和binlog类似)


image.png

1.存在redo log buffer中,物理上是在MySQL进程内存中
2.write到文件系统的page cache中,但是没有进行实际写盘操作(fsync)
3.持久化到磁盘,写盘结束,对应hard disk即绿色

InnoDB 有一个后台线程,每个 1 秒钟 就会将 redo log buffer 中的日志,调用 write 写入到 文件系统的 page cache 中,然后再调用 fsync 持久化到磁盘中。redo log buffer 是共享的,因此一些正在执行中的事务的 redo log 也有可能被持久化到磁盘中。

innoDB根据参数innodb_flush_log_at_trx_commit,redolog的写入策略有三种可能取值:
1.设置为0的时候,表示每次事务提交时都只是把redo log留在redo log buffer中;
2.设置为1的时候,表示每次事务提交时都将redo log直接持久化到磁盘;
3.设置为2的时候,表示每次事务提交时都只是把redo log写到page cache。

综上,redolog实际的触发fsync写盘操作场景实际为:
1.后台线程的每秒轮询
2.innodb_flush_log_at_trx_commit=1,事务提交就会触发
3.innodb_log_buffer_size 是设置 redo log 大小的参数,当 redo log buffer 达到 innodb_log_buffer_size / 2 时,也会触发一次 fsync

组提交

Mysql利用组提交机制优化磁盘持久化的开销,先介绍几个概念:

  • 日志逻辑序列号(log sequence number,LSN):它是用来对应每个redo log写入点的递增序列号,每次写入长度为length的redo log,LSN就会加上length。LSN也会写到InnoDB的数据页中,来确保数据页不会被多次执行重复的redo log。

下面是 3 个并发事务(trx1、trx2、trx3)在 prepare 阶段都写完了 redo log buffer,然后组提交持久化的过程。其中 3 个事务对应的 LSN 分别是:50、120、160。
1.trx1最先到达,被选为该组的leader;
2.trx1准备提交,此时组里面有3个事务此时LSN变为160
3.trx1去写盘的时候,带的就是LSN=160,因此等trx1返回时,所有LSN小于等于160的redo log,都已经被持久化到磁盘;
4.trx2和trx3的区域都被持久化了,可以直接返回了,不用再走一遍流程了

image.png
image.png
image.png

所以,一次组提交里面,组员越多,节约磁盘IOPS的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。

在并发更新场景下,第一个事务写完redo log buffer以后,接下来这个fsync越晚调用,组员可能越多,节约IOPS的效果就越好(这是不是跟上文所提到的fsync_binlog=N,但N为单位时间内能积累的redolog数量机制很像)。
为了让一次fsync带的组员更多,MySQL有一个很有趣的优化:拖时间。回到上面的两阶段提交:


image.png

我们通过binlog的写入机制了解到,
1.实际binlog先从binlog cache写入到binlog files中(write),
2.再调用fsync进行写盘持久化

mysql为了让组提交效果更好,把redo log的时间托到了步骤1之后
image.png

这么一来,binlog也可以组提交了。在执行图中第4步把binlog fsync到磁盘时,如果有多个事务的binlog已经写完了(利用redo log的写盘拖时间,redo log利用 binglog的write托时间),也是一起持久化的,这样也可以减少IOPS的消耗。

不过通常情况下第3步执行得会很快,所以binlog的write和fsync间的间隔时间短,导致能集合到一起持久化的binlog比较少,因此binlog的组提交的效果通常不如redo log的效果那么好。

如果你想提升binlog组提交的效果,可以通过设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count来实现。
1.binlog_group_commit_sync_delay参数,表示延迟多少微秒后才调用fsync;
2.binlog_group_commit_sync_no_delay_count参数,表示累积多少次以后才调用fsync。
这两个条件是或的关系,也就是说只要有一个满足条件就会调用fsync。
所以,当binlog_group_commit_sync_delay设置为0的时候,binlog_group_commit_sync_no_delay_count也无效了。

小结

WAL机制主要得益于两方面:
1.redo log和bin log是顺序写,磁盘的顺序写比随机写速度快;
2.组提交机制,大幅降低磁盘的IOPS消耗
如果你的MySQL现在出现了性能瓶颈,而且瓶颈在IO上,可以通过哪些方法来提升性能呢?

针对这个问题,可以考虑以下三种方法:
1.设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count参数,减少binlog的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
2.将sync_binlog 设置为大于1的值(比较常见是100~1000)。这样做的风险是,主机掉电时会丢binlog日志。
3.将innodb_flush_log_at_trx_commit设置为2。这样做的风险是,主机掉电的时候会丢数据。

不建议把innodb_flush_log_at_trx_commit 设置成0。因为把这个参数设置成0,表示redo log只保存在内存中,这样的话MySQL本身异常重启也会丢数据,风险太大。而redo log写到文件系统的page cache的速度也是很快的,所以将这个参数设置成2跟设置成0其实性能差不多,但这样做MySQL异常重启时就不会丢数据了,相比之下风险会更小。

结合change buffer复习

我们先简化抽象一个写操作流程:
1.从磁盘中读取待变更的行所在数据页到内存
2.对内存页进行操作执行变更
3.将变更后的数据页写入磁盘
步骤1涉及了磁盘的随机读磁盘IO
步骤3涉及随机写磁盘IO
change buffer优化了步骤1,当数据不在内存页时,直接在change buffer中记录变更,减少了随机读磁盘IO
Redo log机制优化了步骤3,将磁盘的随机写优化为顺序写盘,减少了磁盘随机写IO(更别说加上组提交机制)
两者分别从读写角度提升性能,
但是注意用到了change buffer时,在redo log中记录的本次变更是记录 new change buffer item相关信息,而不算直接记录物理页的变更

你可能感兴趣的:(mysql重学(二)二阶段日志)