(六)MySQL日志篇-2:MySQL的日志是做什么用的?(redo log, undo log, binlog)

一篇文章讲清楚MySQL的日志作用

  1. undo log

  2. redo log

  3. binlog

前提概要

在日志篇的开头,我们详细分析了一条sql是如何执行的 ,搞明白这个,是理解MySQL三大日志(redo log, undo log, binlog)的基石,感兴趣的小伙伴可以看看这篇博客 一条sql是如何执行的?

在本篇章,三大日志是我们的重点,同时还有一些辅助日志的介绍,整体的架构如下图!

undo log存在的意义

undo log是什么?它具体放在哪里?作用是什么?

undo log的作用

要搞明白undo log的作用,我们首先要清楚undo log日志里都记录了什么, 为了方便理解,我们可以这么认为:

  • 当执行insert语句时,undo log会记录对应的delete语句
  • 当执行delete语句时,undo log会记录对应的insert语句
  • 当执行update语句时,undo log会记录反的update语句,例如:执行的是 update A to B,记录的是 update B to A

因此,undo log的作用也呼之欲出了。

作用1:保证事务的原子性

如果一条事务里有两条update语句,执行了一条崩溃后,MySQL便可以通过undo log日志来找到旧内容,做回滚。

作用2:实现MVCC的关键因素

我们知道,一条记录如果对其不断地做修改,是会存在多个版本的,所有的版本记录都是会记录在undo log日志中,会通过指针把版本记录串成链表,来实现MVCC。具体的MVCC分析:MVCC是如何解决事务并发问题的? 在此不反复叙述。

但其实本质上,undo log记录的不是相反的sql语句(在回滚的时候执行),记录的是被老版本的数据。可以从行数据列存在的隐藏列(roll_pointer)回滚指针来理解:

  • 当执行一条insert语句时,这条记录是第一个版本,因此roll_pointer指向NULL。当发生回滚时,直接通过回滚指针,用NULL来覆盖记录,达到删除记录的作用。
  • 当执行一条delete语句时,同样的,将数据页中的该列的deleted置于1(后续会细讲),再将roll_pointer指向删除前的数据,也可通过指针回滚。

因此,undo log里其实记录的是一堆老数据,通过roll pointer来实现回滚的功能 ,强烈建议看下这篇文章,undo log配合MVCC食用,理解效果更好。MVCC是如何解决事务并发问题的?

undo log存放在哪里?

对于InnoDB引擎创建的表,每张表在磁盘中都有两个文件,xxx.frm,和xxx.ibd。frm文件中存放了表的结构,类型。而ibd文件主要存放了表的数据以及索引相关信息。 并且,ibd中还有一片叫"段"的区域,一张表有128个回滚段,一个回滚段有1024个segment。一条undo log记录对应一个segment。

undo log相关的配置参数

有几个比较重要的undo log参数,需要有点印象

innodb_undo_directory: 指定undo log的存放路径,默认放在ibd文件

innodb_undo_logs: 指定undo log的回滚段的数量,默认为128个

redo log详解

redo log是什么? 为什么需要redo log?redo log是如何工作的?redo log的内存结构?

redo log作为InnoDB引擎特有的日志,也正是因为redo log,让MySQL拥有数据不丢失的能力。(这个也算是数据库的基本保障把,没有还做啥数据库?)该日志也是我们学习MySQL进阶的必要内容

redo log是什么?

假设存在一场景,古代有个老铁开了间客栈,老铁兼任客栈的账房先生,需要记录来客栈的每一位顾客的赊账记录。

假设这时候有生意上门了,有顾客要找老板赊账,此刻老板有两种做法:

  • 拿出账本,找到该顾客的记录,把赊账的记录更新到账本里面,
  • 先用一个临时的本子将记录记下,后续再一起更新。

在生意很忙的时候,为了效率最大化,脑子不笨的老板肯定是会选择做法2。最后在不忙的时候一起更新到账本里(打烊的时候,或者顾客较少时)。

把这个例子套用到MySQL的增删改语句中redo log就等同于临时的本子!

为什么需要redo log?

在MySQL中同样有这个问题,如果每次的数据变更操作(顾客的赊账记录)都要写到磁盘里(账本),都需要先查找记录,再更新记录,最后再回写。一读一写,IO成本太高,影响效率。

因此,MySQL是这样做的:

  • 对于每一笔赊账记录,老板先将这笔帐记录在脑子里(记录写到内存
  • 再写到临时的本子上(redo log日志
  • 最后在不忙的时候,更新到账本里(IO操作刷盘

通过三步走,以此来提高效率。这也是著名的WAL技术 。并且,日志会比数据先写到磁盘里(写日志也是一种IO操作),提供了系统崩溃的时候能保证数据不丢失的能力(crash-safe)后续展开描述

Tips: 记录到redo log的内容为物理日志。例如:在xxxx页做了xxxx修改。而非逻辑日志,直接记录sql

redo log在磁盘中以二进制的格式存在,默认的文件名为ib_logfile(0,1,2....),具体生成的文件个数和大小均可以配置,默认为2个文件,大小为48M(1024 * 48 = 49152K),如下:

redo log是如何工作的?

写redo log日志的形式是顺序写 ,直接追加记录在日志的末尾即可。顺序写表明了数据会不断地被覆盖 ,具体的架构图(顺时针记录,配置了4个redo log日志)如下:

架构的主要成员如下:

  • 指针write pos;表明当前记录的位置,往前推说明有记录被写到redo log了
  • 指针check point:表明当前要持久化的位置,往前推说明有记录从redo log持久化到磁盘了
  • 区域write pos:表明redo log还可以写入的空间,为两根指针中间

当一条sql被执行时,会先将记录写到redo log的末尾(顺序写),即指针write pos会往前推;写完redo log后会返回执行成功,此刻还没有真正入盘。当这条记录被指针check point追上时,表明这条记录被持久化到磁盘了。

于是乎,执行一条数据变更的sql本是要去磁盘做随机写 的(得先磁盘寻址,判断,再写入),通过redo log,变成了日志的顺序写 。从操作系统的角度出发,同样是写操作,随机写的效率是要远小于顺序写的

redo log是如何保证crash safe能力的?

由前面可知,MySQL执行sql后的数据变更并不会马上写到磁盘里,而是会先记录到内存,并写redo log日志,若此刻突然断电,本应该持久化的数据又如何保证不丢失呢? 结合redo log的架构,我们来尝试"还原数据",步骤如下:

  1. 先定位到redo log日志中的双指针(write pos、check point)位置
  2. check point指向的位置,其实就是当前MySQL从内存写到磁盘的位置左边是redo log可写入的区域,右边是已经记录在redo log,但未刷盘的数据
  3. 此刻,从check point往前推进直到和write pos相遇之间的环形数据,均是之前存储在内存的数据,但未刷盘的。
  4. 因此,MySQL只要将check point推进到和write pos一样的位置 ,就能将之前在内存的数据(未写盘的)给持久化到磁盘上,完成"丢失数据的恢复"

redo log是如何刷脏页的?(check point的前移)

什么是脏页?

当内存的数据页和磁盘的数据页内容不一致时(sql已执行,内存存放最新数据,但redo log还未写到磁盘),将这一内存页称之为脏页

check point肯定不会一直原地不动的,也会主动刷盘,将数据持久化到硬盘。移动的时机总结如下:

  • 临时的本子写满了,需要将本子的记录擦掉一些。(对应的是redo log写满了,write pos的空间为0,此时需要将check point往前推一推)(尽量避免,redo log若写满,所有的写操作都会被阻塞,严重影响性能
  • 生意太好了,老板的脑子已经记不下东西。(对应的是内存不足,需要新的内存页 -> 淘汰掉部分页;如果淘汰的是脏页,就需要推动check point)(影响性能
  • 生意不忙的时候,老板闲着也是闲着,把本子拿出来更新下账本。(对应的是MySQL的读写压力不大,redo log主动推进check point)(该情况对性能的影响不大
  • 店面要打烊了,关门。(对应的是MySQL的进程关闭,需要把redo log清一清)(进程都要关了,对性能的要求也不大

总结如下:

MySQL是怎么主动刷脏页的呢?频率又该如何计算?

刷脏页的本质上就是做IO操作,因此有一个参数来控制:innodb_io_capacity,系统的默认值为200。表明告诉MySQL每秒刷200个脏页

这个值是特别重要的,变相告诉了MySQL你这台主机的IO能力。值越低,刷脏页的速度越慢。如果刷脏页的速度小于redo log写入的速度,就会触发MySQL去频繁的被动刷脏页,影响性能。 因此,该值最好是直接设置成你主机的IOPS(每秒的读写次数),可以通过如下命令去检测:

ini 复制代码
fio -filename=$filename -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=16K -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest

在我本机执行如下:

我自己的电脑的IOPS是4161,因此可直接将innodb_io_capacity设为4000.

但即使是你将innodb_io_capacity设为4000,MySQL也不可能全力去按4000的速度刷脏页,毕竟还需要提供其它服务,不止是刷脏页,具体的速度计算如下:

kotlin 复制代码
        V(m) = if (m > innodb_max_dirty_pages_pct)
                    return 100;
               else
                   return 100 * m / innodb_max_dirty_pages_pct;

上述公式:m代表当前的脏页比例;innodb_max_dirty_pages_pct代表脏页比例上限(默认是75%)。因此,该公式的含义为:当系统的脏页比例大于设定的阈值,开始全力刷脏页!

当前的脏页比例通过:show global_status获取,如下:

对于MySQL来说,无时无刻充满了各种优化,包括这个刷脏页的策略。MySQL存在一个优化策略,由innodb_flush_neighbors参数控制,默认是开启的

当该参数开启时,刷脏页会有连坐机制 ,如果此刻要刷的脏页的邻居也是脏页,会顺手一起刷掉,并且还可以蔓延,邻居的邻居也可以覆盖。 通过该手段,减少了大量随机IO,也能整体带动系统性能

redo log的刷盘策略(写入时机)

在执行sql,写redo log日志时,也不是直接把redo log直接写到磁盘的,而是写到了一个缓存池buffer里 。因此,redo log把buffer的数据刷到磁盘里,也是存在对应的策略的

PS:这里的刷盘策略不同于上面的刷脏页策略。刷脏页策略的本质是将行数据持久化,而刷盘策略的本质是将redo log日志持久化

redo log中的一条记录的状态可能会经历三阶段,如下:

一、 在开始阶段,记录先被写到redo log buffer中,此刻该记录存在MySQL的进程内存中,未被持久化。(如果MySQL进程崩溃,数据可能会丢失

二、记录从buffer中写到(write)到系统中的page cache,对应的是操作系统。(已经从MySQL转移出来,但此刻服务器若宕机,数据仍有丢失的风险

三、记录从page cache中持久化(fsync)到磁盘,对应的就是彻底持久化。(此刻服务器宕机或者MySQL崩溃,数据都不会丢失

具体的持久化策略由参数:innodb_flush_log_at_trx_commit控制。具体策略如下:

  • 当设置为0时,表明每次事务提交时,redo log只会被记录到redo log buffer中(极度不安全)
  • 当设置为1时,表明每次事务提交时,redo log会直接fsync到硬盘中(最安全)
  • 当设置为2时,表明每次事务提交时,redo log会被write到page cache中

除了由参数控制的主动刷盘策略,redo log还存在被动的刷盘策略,redo log buffer是存在于buffer pool中的一块内存空间,是线程共享的,(有点类似JVM中的堆空间):

  • InnoDB存在一后台线程,每隔一秒,会把redo log buffer中的数据全都fsync到磁盘。(表明redo log buffer中的数据不会呆超过1s
  • redo log buffer占用的空间达到innodb_log_buffer_size的一半,后台线程就会清场,把数据都write到page cache中
  • 事务提交时,write或者fsync的最小颗粒度是整个redo log buffer。因此,可能会顺带将其它事务的redo log一起持久化

无论是被动还是主动,总结如下:

redo log buffer结构剖析

上面我们详细介绍了redo log的刷盘策略(离不开redo log buffer),redo log buffer由无数个redo log block组成(可以简单将block理解成一个page),总大小为512字节。

而一个block的具体结构如下:

log block header : 存放这个block的一些基础信息(标识符,body的具体占用字节,check point的序号等),占12B

log block body: 存放具体的redo log记录,占496B

log block tail: check_sum,校验和,验证这个block是否完整,在各大协议中校验和也是比较常见的,占4B

redo log相关的配置参数

有几个比较重要的redo log参数,可能会在工作中用到,需要有点印象

innodb_flush_log_at_trx_commit: 指定redo log的刷盘策略,默认为1

innodb_log_group_home_dir: 指定redo log的默认保存路径,默认在根目录下

innodb_log_buffer_size: 指定redo log buffer的大小,默认为16M

innodb_log_files_in_group: 指定redo log日志的磁盘文件个数,默认为2

innodb_log_files_size: 指定redo log日志的每个磁盘文件的大小,默认为48M

innodb_io_capacity: 指定MySQL刷脏页的速度,默认为200

binlog详解

binlog是什么?binlog的作用?有了binlog为什么还要redo log??

当我们提到binlog时,往往是拿redo log来跟它做比较。同样都是记录数据,也都有刷盘策略;下面我们一步一步来揭开binlog

什么是binlog?

不同于redo log(InnoDB引擎独有),binlog是MySQL原生的日志 。和redo log日志类似,binlog也是用来记录数据 ,但没有redo log复杂的双指针模式,binlog采用追加写 的形式,不会覆盖之前的数据,当文件写满时,会创建新的文件继续写,如下:

在磁盘中以二进制 的形式存在,初始文件名为mysql-bin.0000001。随着文件的增多,后面数字不断递增。由于其追加写的形式,binlog主要应用于数据备份,主从库间的复制

在MySQL中,binlog默认是关闭的,可通过 log_bin 查看情况,需要往mysql的配置文件中添加如下配置:

ini 复制代码
        [mysqld]
        # 开启binlog
        log_bin=ON  
        # binlog的记录形式(后续重点)
        binlog_format=mixed
        # binlog的保存路径
        log_bin=/var/lib/mysql/mysql-bin
        # mysql的id(在集群模式下需唯一<img src=")" alt="" width="70%" />
        server-id=123
        # 存放时间
        expire_logs_day=30
        # 单个文件的最大大小
        max_binlog_size=50m

将如上的配置加上后,重启MySQL,即可看到如下,表明binlog日志已开启。

记录binlog的三种格式介绍

在上面的binlog配置项中,其中有一项是binlog_format,代表binlog在记录数据时的数据格式 ,有三种格式,分别是statement,row,mixed

为了方便区分三种格式,我初始化了一些数据,一张user表,存在字段id,name,age,male,数据如下:

statement

当binlog_format=statement时,binlog里记录的就是sql语句的原文,执行一条sql语句如下:

sql 复制代码
                    delete from user where id > 2 limit 2; (执行)

执行完毕后,可通过:show master status; 获取当前正在写的binlog日志

最后,通过如下命令,直接查看binlog日志的最底部

go 复制代码
                    show binlog events in `mysql-bin.000018`;

结果如下:

结果如上图,binlog里直接记录了真实执行的sql原句,加上数据库,以及事务的开始和结束指令。

row

当binlog_format=row时,binlog里记录的是数据的全量信息,再执行相同的sql,通过命令查看到:

原来的sql语句已经没有了,取而代之的是记录了table_map和delete_rows,其中:

  • table_map: 记录了具体操作的数据库和表
  • delete_rows: 定义了删除数据的行为

并且可以看出,删除数据的行为是在binlog的第844行开始的。因此,我们借助原生的mysqlbinlog命令了解具体信息,如下:

css 复制代码
            mysqlbinlog --no-defaults -vv mysql-bin.000019 --start-position=844

结果如下:

对比row和statement

对比两种不同的格式,我们可以发现有以下区别:

  1. 当格式为statement时,binlog记录的是原始的sql语句,如果sql语句包含limit,自增主键,时间戳(now())等条件,很有可能会出现主备不一致的问题,导致数据不一致。

    如下,在执行完sql,通过:show warnings; 可看出:

  1. 当格式为row时,binlog记录的是全量的语句。如果碰到批量删除 or 增加的sql,例如:用一条delete语句删除10W条数据,binlog对应的会记录10W条记录,(statement只会记录sql)。会占用极大的空间

mixed

由于statement和rows都有各自的优缺点,无法很准确地定义出谁好谁坏。因此,MySQL取了一种折中的方案,mixed格式出现了

我们害怕statement产生的主备不一致问题,又害怕row产生的空间占用率大的问题。因此,当binlog_format=mixed时,把选择权交给MySQL,让其自行判断这条sql语句是否会产生主备不一致的问题。如果会,就采用row格式记录数据,反之则采用statement格式记录数据。

binlog的刷盘策略(写入时机)

MySQL在写redo log的时候,是会经过redo log buffer -> page cache -> disk的过程的。同样的,binlog也是类似,MySQL在写binlog时,同样不会直接将其写到硬盘,一条记录可能的状态如下:

一、 在开始阶段,记录先被写到binlog cache中,此刻该记录存在MySQL的进程内存中,未被持久化。(binlog cache是线程私有的,非共享。这点和redo log buffer有区分

二、记录从buffer中写到(write)到系统中的page cache,对应的是操作系统。

三、记录从page cache中持久化(fsync)到磁盘,对应的就是彻底持久化。

为什么binlog cache是线程私有的?

我的理解是binlog的主要功能为数据备份,需要存储的是全量数据;执行的时候肯定是要按一个完整的事务来的,不能穿插。(因此,每个执行事务的线程都需要有独自的cache )。如果搞成共享的,极有可能会出现向redo log那样被动提交的情况。

具体的持久化策略由参数:sync_binlog控制。具体策略如下:

  • 当设置为0时,表明每次事务提交时,binlog只会write到page cache中
  • 当设置为1时,表明每次事务提交时,binlog会直接fsync到硬盘中(最安全)
  • 当设置为N时,表明每次事务提交时,binlog只会write,累积N个事务后才会一起fsync

Tips: 一般我们使用binlog,sync_binlog都是置为1。但是当数据库出现IO瓶颈时,可以临时将其设置为N,减小IO的压力。但面临的危险是,一旦服务器崩溃,可能会丢失最近N个事务的记录。

binlog做数据恢复

记录几个比较简单的数据恢复命令,按时间 or 按具体的行数,全量复制的命令如下:

ini 复制代码
            mysqlbinlog --database=xxx mysql-bin.0000xx | mysql -uxxx -pxxx

按具体行数,可添加参数如下:

arduino 复制代码
                    --start-position=xxx      --end-position=xxx

按时间命令,可添加参数如下:

ini 复制代码
                    --start-datetime=xxx (timestamps or datetime)

有了binlog,为什么还需要redo log?

这个真的是一个非常经典的问题了。出于常识,MySQL不会吃饱撑着,所以肯定是两者都需要的,侧重点不同。redo log让MySQL拥有断电不丢失数据的能力;binlog让MySQL拥有数据备份的能力

可以没有redo log,只有binlog吗?

既然binlog全量记录了数据,那可以将redo log拿掉,用binlog来做断电恢复数据吗?

这肯定是不行的,如果MySQL突然发生异常重启,仅靠binlog是没办法保证数据不丢失的,理由如下:

  • MySQL做的数据变更操作,为了效率,是不会马上写到磁盘,而是会保留在内存的。
  • 由于binlog是追加写,没有像redo log那样拥有双指针,可以准确定位到哪部分的数据还没有写到磁盘,就像无头苍蝇一样,也不可能把整个文件都跑一遍。
  • 因此,宕机前存储在内存的数据(未持久化的那一块),会丢失。

可以没有binlog,只有redo log吗?

这也是不可能的,因为redo log是顺序写,像绕圈一样,当前的记录会覆盖掉过去的记录,没办法做到数据的完整记录,不具备像binlog那样的归档能力。

binlog和redo log区别总结

总结如下:

辅助日志介绍

slow log

slow log,即慢查询日志。当一条sql执行时间超过设定的阈值时,该sql就会被记录在slow log中

假设执行一条sql: select sleep(10); 休息10s,慢查询日志内容如下:

慢查询日志中,会记录具体的sql,以及花费的查询时间(Query_time)和加锁时间(Lock_time)

slow log的相关配置参数如下:

slow_query_log: 设置是否开启慢查询日志,默认值是OFF,关闭的

show_query_log_file: 指定慢查询日志的存储目录及文件名,默认是放在具体数据库的目录下

long_query_time: sql执行时间的阈值 ,默认值是10s,(slow log在MySQL中是没有缓存的,会实时的IO写盘,因此要合理设置该值,避免IO压力

error log

错误日志,用于记录系统的报错信息,包括MySQL进程的启动,以及停止等等

截图部分error log,同时还会记录MySQL运行的各种信息,如下:

error log的相关配置参数如下:

log_error: 指定错误日志的存储目录以及文件名,默认值是放在data目录下

general log

general log,用于记录MySQL执行的查询日志,无论结果如何,都会记录

截图部分general log,如下:

general log直接记录sql,逻辑记录

general log的相关配置参数如下:

general_log: 是否开启查询日志 ,默认值是OFF,关闭的(为了性能,默认是关闭

general_log_file: 指定查询日志的存储目录及文件名,默认值是放在data目录下

relay log

中继日志,仅存在主从架构的从机上,日志存在的内容,形式,架构和binlog日志保持一致。具体的形式如下:

  1. 在主从架构数据同步时,从机会接收主机的binlog日志数据。
  2. 接收的数据会放在从机的relay log内。
  3. 从机有专门的线程池去消费relay log的内容,完成主从复制。

详解二阶段提交

再回到我们一开始的问题,我们学习redo log, binlog, undo log日志,从某种意义上就是为了让我们能更好的理解一条sql的具体执行过程。而最后一步,就是二阶段提交

详细的内容如下:

二阶段提交的基本流程

基于前面对redo log和binlog的详解,我们进一步细化执行一条update语句在内存更新后的流程 ,大致的流程可以参考下这篇文章一条sql语句是怎么样执行的?

具体的流程图如下:

二阶段提交!即将redo log的写入拆成了prepare, commit两个状态

一、将新数据写到内存后,执行器调用innoDB引擎提供的接口,先记录undo log日志。

二、执行器调用innoDB引擎提供的接口,将物理记录写到redo log日志内。此刻,redo log处于prepare状态,并告诉执行器执行OK。

三、执行器将记录写到binlog日志内。写完后,调用innoDB的接口,将redo log日志的状态从prepare变为commit。

总结来说 -> 二阶段,就是把redo log的写入拆成两步:prepare和commit,中间穿插写binlog

prepare阶段:将执行该条sql的事务XID写到redo log,根据设定的刷盘策略,写入redo log,然后将redo log日志的状态设为prepare。

commit阶段 :执行器写binlog(包含事务的XID),而后调用innoDB提供的提交事务接口,将redo log日志的状态设为commit。(此commit并非是事务提交的commit状态,事务的commit状态标识了这个事务的完整结束;而二阶段提交的commit阶段是代表了当前事务的记录被完整记录到binlog和redo log中

为什么需要二阶段提交?

为什么MySQL要把一个提交过程搞得这么复杂呢?肯定是有原因的,就是保证数据的一致性,若发生半成功现象,必然导致数据不一致!

假设没有二阶段提交,先写redo log,再写binlog,如下:

如果在写完redo log,在时刻A宕机了。此刻,binlog尚未写入,MySQL重启的时候可以通过redo log还原断电丢失的数据但在主从复制的时候,由于binlog缺失数据,就会出现主备不一致的问题

反过来,先写binlog,再写redo log,如下:

如果在写完binlog,在时刻A宕机了。此刻,redo log尚未写入,MySQL重启的时候可以由于redo log尚未写入,原来的数据无法恢复但在主从复制的时候,由于binlog没有丢数据,也会出现主备不一致的问题

因此,二阶段提交还是很有必要的!

二阶段提交时,系统崩溃了...

再回顾一下完整的提交流程,

下面我们来看下,有二阶段提交的前提下,系统崩溃了会发生什么现象?

在时刻A及以前崩溃,此刻redo log里面有数据,binlog里未写入。因此,在系统恢复后,该事务必然是回滚的。

关键是在时刻B崩溃了 ,此刻的redo log其实已经写入了,是否回滚的关键在binlog。判断逻辑如下:

  • 如果此刻执行器已经调用了提交事务的接口(redo log的状态为commit),此刻binlog日志已写入,而redo log的状态也over,事务直接提交!
  • 如果此刻执行器还未调用提交事务的接口 ,redo log的状态还是prepare。则需要判断:对应的binlog日志中,是否有commit的内部事务XID
    • 如果有,则事务提交!
    • 如果没有,表明binlog未完整写入,事务回滚!

如下:在binlog日志的最后一行,事务的commit阶段都是会带上XID的

回滚的关键是查看binlog是否完整! 具体的判断流程如下:

二阶段提交引起的问题

如果是在双1配置下(redo log和binlog的刷盘策略都为1),每次redo log和binlog的更新,都会强制将数据fsync持久化到磁盘上。

因此一个事务在提交时,需要经历两次刷盘(redo log和binlog各一次),保证数据的强一致性。

如果MySQL的TPS为2000/s时,每秒的IO刷盘次数为4000。对MySQL肯定是灾难级别的性质了,会带来严重的性能影响。

优化------组提交机制

MySQL肯定不会放任这种问题存在,也是引入了组提交机制来解决,具体的流程如下:

假设存在三个并发的事务:trx1、trx2、trx3,在二阶段提交的prepare阶段,都会将各自事务的记录写到redo log buffer里(buffer是共享的,因此记录都在同一个buffer )。在写入的时候,会带上一个LSN值,该值的计算方法为:上一个事务的LSN值 + 本次提交事务的记录长度

一、假设先到的trx_1是队长,后到的trx_2和trx_3是小弟。

二、看下手表,差不多到时间了,由队长去写盘,会带着最小的trx_3的LSN值去。等到队长回来了,就表明LSN<150的数据都被持久化到磁盘去了。

三、trx_2和trx_3的数据都被trx_1一起持久化了,因此直接返回,不用再刷盘。

Tips: 类似TCP的累积确认机制,当一组的成员越多,越节约磁盘IO的次数。

组提交机制的内置优化手段

当一组的成员越多,肯定是越节约磁盘IO的次数的。因此,在队长先到redo log buffer后,后续的fsync越晚,拖得越久,所积累的小弟也就越多!

而原先二阶段提交中的写binlog,也可以拆分成两步:write(写page cache) and fsync(写disk)

因此,可将二阶段提交的步骤可被优化成如下:

原先在redo log的prepare阶段,就会直接完成fsync的步骤。为了尽可能最大化组提交的成员数量,将fsync迁移到binlog的write和fsync中间。 与此同时,binlog也变相进行了组提交(中间插入了redo log的fsync),算得上是一举多得。

组提交机制的参数优化手段

想更好的提高binlog的组提交效率,可以通过两个内置参数完成。如下:

binlog_group_commit_sync_delay: 表明binlog延迟多少微秒后调用fsync,对应步骤4

binlog_group_commit_sync_no_delay_count: 表明累计多少次后调用fsync,同样对应步骤4

两者是或的关系,只要满足其中一个条件就会调用binlog的fsync

二阶段提交的总结

当MySQL碰到IO瓶颈时,有以下两种优化方法:

  1. 合理设置binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count的值,减少binlog的IO次数

  2. 短暂修改双1策略,将sync_binlog设置大于1的值(减少binlog的持久化次数,累计N个),将innodb_flush_log_at_trx_commit设置为2,redo log只会write到page cache,不会去持久化硬盘。

相关推荐
Ai 编码助手5 分钟前
MySQL中distinct与group by之间的性能进行比较
数据库·mysql
陈燚_重生之又为程序员20 分钟前
基于梧桐数据库的实时数据分析解决方案
数据库·数据挖掘·数据分析
caridle22 分钟前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
白云如幻23 分钟前
MySQL排序查询
数据库·mysql
萧鼎25 分钟前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
^velpro^27 分钟前
数据库连接池的创建
java·开发语言·数据库
苹果醋331 分钟前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
荒川之神32 分钟前
ORACLE _11G_R2_ASM 常用命令
数据库·oracle
IT培训中心-竺老师38 分钟前
Oracle 23AI创建示例库
数据库·oracle
小白学大数据1 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫