美文网首页
mysql之二阶段日志

mysql之二阶段日志

作者: 温岭夹糕 | 来源:发表于2021-03-23 22:37 被阅读0次

    前言

    我们在前文介绍了普通索引能利用chang buffer减少IO的随机读写,其实innodb的redo log也能做到。

    顺序读写和随机读写

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

    两阶段提交

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


    image.png

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

    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之二阶段日志

          本文链接:https://www.haomeiwen.com/subject/lvmpqltx.html