原文:https://jin-yang.github.io/post/mysql-group-commit.html

组提交 (group commit) 是为了优化写日志时的刷磁盘问题,从最初只支持 InnoDB redo log 组提交,到 5.6 官方版本同时支持 redo log 和 binlog 组提交,大大提高了 MySQL 的事务处理性能。

下面将以 InnoDB 存储引擎为例,详细介绍组提交在各个阶段的实现原理。

简介

自 5.1 之后,binlog 和 innodb 采用类似两阶段提交的方式,不过不支持 group commit;在 5.6 中,将 binlog 的 commit 阶段分为三个阶段:flush stage、sync stage 以及 commit stage。

这三个阶段中,每个阶段都会去维护一个队列,各个列表的定义如下。

Mutex_queue m_queue[STAGE_COUNTER];

如上,每个阶段都在维护一个队列,第一个进入该队列的作为 leader 线程,否则作为 follower 线程;leader 线程会收集 follower 的事务,并负责做 sync,follower 线程等待 leader 通知操作完成。

尽管维护了三个队列,但队列中所有的 THD 实际上都是通过 next_to_commit 连接起来。binlog 在事务提交阶段,也就是在 MYSQL_BIN_LOG::ordered_commit() 函数中,开始 3 个阶段的流程。

接下来,看看 MySQL 中事务是如何提交的。

事务提交

接下来,看看 InnoDB 和 binlog 提交的流程。

二阶段提交

详细介绍下二阶段提交的过程。

未开启binlog时

InnoDB 通过 redo 和 undo 日志来恢复数据库 (safe crash recovery),当数据恢复时,通过 redo 日志将所有已经在存储引擎内部提交的事务应用 redo log 恢复,所有已经 prepared 但是没有 commit 的事务则会通过 undo log 做回滚。

然后客户端连接时就能看到已经提交的数据存在数据库内,未提交被回滚地数据需要重新执行。

开启binlog时

为了保证存储引擎和 MySQL 的 binlog 保持一致,引入二阶段提交 (two phase commit, 2pc) 。

因为备库通过 binlog 重放主库提交的事务,假设主库存储引擎已经提交而 binlog 没有保持一致,则会使备库数据丢失造成主备数据不一致。

二阶段提交

如下是二阶段提交流程。

详细执行流程为:

  1. InnoDB 的事务 Prepare 阶段,即 SQL 已经成功执行并生成 redo 和 undo 的内存日志;

  2. binlog 提交,通过 write() 将 binlog 内存日志数据写入文件系统缓存;

  3. fsync() 将 binlog 文件系统缓存日志数据永久写入磁盘;

  4. InnoDB 内部提交,commit 阶段在存储引擎内提交,通过 innodb_flush_log_at_trx_commit 参数控制,使 undo 和 redo 永久写入磁盘。

开启 binlog 的 MySQL 在崩溃恢复 (crash recovery) 时:

  • 在 prepare 阶段崩溃,恢复时该事务未写入 binlog 且 InnoDB 未提交,该事务直接回滚;

  • 在 binlog 已经 fsync() 永久写入 binlog,但 InnoDB 未来得及 commit 时崩溃;恢复时,将会从 binlog 中获取提交的信息,重做该事务并提交,使 InnoDB 和 binlog 始终保持一致。

以上提到单个事务的二阶段提交过程,能够保证 InnoDB 和 binlog 保持一致,但是在并发的情况下怎么保证存储引擎和 binlog 提交的顺序一致?当并发提交的时,如果两者不一致会造成什么影响?

组提交异常

首先看看,对于上述的问题,当并发提交的时,如果两者不一致会造成什么影响?

如上所示,事务按照 T1、T2、T3 顺序开始执行,并依相同次序按照写入 binlog 日志文件系统缓存,调用 fsync() 进行一次组提交,将日志文件永久写入磁盘。

但是存储引擎提交的顺序为 T2、T3、T1,当 T2、T3 提交事务之后做了一个 On-line 的备份程序新建一个 slave 来做复制;而搭建备库时,CHANGE MASTER TO 的日志偏移量在 T3 事务之后。

那么事务 T1 在备机恢复 MySQL 数据库时,发现 T1 未在存储引擎内提交,那么在恢复时,T1 事务就会被回滚,此时就会导致主备数据不一致。

结论:需要保证 binlog 的写入顺序和 InnoDB 事务提交顺序一致,用于 xtrabackup 备份恢复。

早期解决方案

早期,使用 prepare_commit_mutex 保证顺序,只有当上一个事务 commit 后释放锁,下个事务才可以进行 prepara 操作,并且在每个事务过程中 binlog 没有 fsync() 的调用。

由于内存数据写入磁盘的开销很大,如果频繁 fsync() 把日志数据永久写入磁盘,数据库的性能将会急剧下降。为此提供 sync_binlog 参数来设置多少个 binlog 日志产生的时候调用一次 fsync() 把二进制日志刷入磁盘来提高整体性能,该参数的设置作用为:

  • sync_binlog=0,二进制日志 fsync() 的操作基于系统自动执行。

  • sync_binlog=1,每次事务提交都会调用 fsync(),最大限度保证数据安全,但影响性能。

  • sync_binlog=N,当数据库崩溃时,可能会丢失 N-1 个事务。

prepare_commit_mutex 的锁机制会严重影响高并发时的性能,而且 binlog 也无法执行组提交。

改进方案

接下来,看看如何保证 binlog 写入顺序和存储引擎提交顺序是一致的,并且能够进行 binlog 的组提交?5.6 引入了组提交,并将提交过程分成 Flush stage、Sync stage、Commit stage 三个阶段。

这样,事务提交时分为了如下的阶段:

InnoDB, Prepare
SQL已经成功执行并生成了相应的redo和undo内存日志;
Binlog, Flush Stage
所有已经注册线程都将写入binlog缓存;
Binlog, Sync Stage
binlog缓存将sync到磁盘,sync_binlog=1时该队列中所有事务的binlog将永久写入磁盘;
InnoDB, Commit stage
leader根据顺序调用存储引擎提交事务;

每个 Stage 阶段都有各自的队列,从而使每个会话的事务进行排队,提高并发性能。

如果当一个线程注册到一个空队列时,该线程就做为该队列的 leader,后注册到该队列的线程均为 follower,后续的操作,都由 leader 控制队列中 follower 行为。

leader 同时会带领当前队列的所有 follower 到下一个 stage 去执行,当遇到下一个 stage 为非空队列时,leader 会变成 follower 注册到此队列中;注意:follower 线程绝不可能变成 leader 。

配置参数

与 binlog 组提交相关的参数主要包括了如下两个参数。

binlog_max_flush_queue_time

单位为微妙,用于从 flush 队列中取事务的超时时间,这主要是防止并发事务过高,导致某些事务的 RT 上升,详细的内容可以查看函数 MYSQL_BIN_LOG::process_flush_stage_queue() 。

注意:该参数在 5.7 之后已经取消了。

binlog_order_commits

当设置为 0 时,事务可能以和 binlog 不同的顺序提交,其性能会有稍微提升,但并不是特别明显.

源码解析

binlog 的组提交是通过 Stage_manager 管理,其中比较核心内容如下。

class Stage_manager {
public:
enum StageID { // binlog的组提交包括了三个阶段
FLUSH_STAGE,
SYNC_STAGE,
COMMIT_STAGE,
STAGE_COUNTER
};
private:
Mutex_queue m_queue[STAGE_COUNTER];
};

组提交 (Group Commit) 三阶段流程,详细实现如下。

MYSQL_BIN_LOG::ordered_commit()           ← 执行事务顺序提交,binlog group commit的主流程
|
|-#########>>>>>>>>> ← 进入Stage_manager::FLUSH_STAGE阶段
|-change_stage(..., &LOCK_log)
| |-stage_manager.enroll_for() ← 将当前线程加入到m_queue[FLUSH_STAGE]中
| |
| | ← (follower)返回true
| |-mysql_mutex_lock() ← (leader)对LOCK_log加锁,并返回false
|
|-finish_commit() ← (follower)对于follower则直接返回
| |-ha_commit_low()
|
|-process_flush_stage_queue() ← (leader)对于follower则直接返回
| |-fetch_queue_for() ← 通过stage_manager获取队列中的成员
| | |-fetch_and_empty() ← 获取元素并清空队列
| |-ha_flush_log()
| |-flush_thread_caches() ← 对于每个线程做该操作
| |-my_b_tell() ← 判断是否超过了max_bin_log_size,如果是则切换binlog文件
|
|-flush_cache_to_file() ← (follower)将I/O Cache中的内容写到文件中
|-RUN_HOOK() ← 调用HOOK函数,也就是binlog_storage->after_flush()
|
|-#########>>>>>>>>> ← 进入Stage_manager::SYNC_STAGE阶段
|-change_stage()
|-sync_binlog_file()
| |-mysql_file_sync()
| |-my_sync()
| |-fdatasync() ← 调用系统API写入磁盘,也可以是fsync()
|
|-#########>>>>>>>>> ← 进入Stage_manager::COMMIT_STAGE阶段
|-change_stage() ← 该阶段会受到binlog_order_commits参数限制
|-process_commit_stage_queue() ← 会遍厉所有线程,然后调用如下存储引擎接口
| |-ha_commit_low()
| |-ht->commit() ← 调用存储引擎handlerton->commit()
| | ← ### 注意,实际调用如下的两个函数
| |-binlog_commit()
| |-innobase_commit()
|-process_after_commit_stage_queue() ← 提交之后的后续处理,例如semisync
| |-RUN_HOOK() ← 调用transaction->after_commit
|
|-stage_manager.signal_done() ← 通知其它线程事务已经提交
|
|-finish_commit()

在 enroll_for() 函数中,刚添加的线程如果是队列的第一个线程,就将其设置为 leader 线程;否则就是 follower 线程,此时线程会睡眠,直到被 leader 唤醒 (m_cond_done) 。

注意,binlog_max_flush_queue_time 参数已经取消。

commit stage

如上所述,commit 阶段会受到参数 binlog_order_commits 的影响,当该参数关闭时,会直接释放 LOCK_sync ,各个 session 自行进入 InnoDB commit 阶段,这样不会保证 binlog 和事务 commit 的顺序一致。

当然,如果你不关注两者的一致性,那么可以关闭这个选项来稍微提高点性能;当打开了上述的参数,才会进入 commit stage 。

(转)MySQL 日志组提交的更多相关文章

  1. 自导自演的面试现场之--你竟然不了解MySQL的组提交?

    Hi,大家好!我是白日梦!本文是MySQL专题的第 26 篇. 下文还是白日梦以自导自演的方式,围绕"组提交"展开本话题.看看你能抗到第几问吧 换一种写作风格,自导自演面试现场!感 ...

  2. MySQL binlog 组提交与 XA(两阶段提交)

    1. XA-2PC (two phase commit, 两阶段提交 ) XA是由X/Open组织提出的分布式事务的规范(X代表transaction; A代表accordant?).XA规范主要定义 ...

  3. MySQL binlog 组提交与 XA(分布式事务、两阶段提交)【转】

    概念: XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口.XA为了实现分布 ...

  4. MySQL binlog 组提交与 XA(两阶段提交)--1

    参考了网上几篇比较靠谱的文章 http://www.linuxidc.com/Linux/2015-11/124942.htm http://blog.csdn.net/woqutechteam/ar ...

  5. mysql复制那点事(2)-binlog组提交源码分析和实现

    mysql复制那点事(2)-binlog组提交源码分析和实现 [TOC] 0. 参考文献 序号 文献 1 MySQL 5.7 MTS源码分析 2 MySQL 组提交 3 MySQL Redo/Binl ...

  6. MySQL Replication--事务组提交和多线程复制

    事务组提交和多线程复制 在MySQL 5.7版本引入基于LOGICAL_CLOCK的多线程复制,依赖于BINLOG事件中的last_committed属性,该last_committed属性是否与事务 ...

  7. mysql并发复制系列 一:binlog组提交

    http://blog.itpub.net/28218939/viewspace-1975809/ 作者:沃趣科技MySQL数据库工程师  麻鹏飞 MySQL  Binary log在MySQL 5. ...

  8. innodb二阶段日志提交机制和组提交解析

    前些天在查看关于innodb_flush_log_at_trx_commit的官网解释时产生了一些疑问,关于innodb_flush_log_at_trx_commit参数的详细解释参见官网: htt ...

  9. MySQL崩溃恢复与组提交

      Ⅰ.binlog与redo的一致性(原子) 由内部分布式事务保证 我们先来了解下,当一个commit敲下后,内部会发生什么? 步骤 操作 step1 InnoDB做prepare redo log ...

随机推荐

  1. 编写高质量代码改善C#程序的157个建议——建议107:区分静态类和单例

    建议107:区分静态类和单例 有一种观点认为:静态类可以作为单件模式的一种实现方式.事实上,这是不妥当的.按照传统的观点来看,单例是一个实例对象.而静态类并不满足这一点.静态类也直接违反面向对象三大特 ...

  2. 【小梅哥SOPC学习笔记】设置Eclipse在编译(build)前自动保存源代码文件

    设置Eclipse在编译(build)前自动保存源代码文件 Eclipse 常用设置之让Eclipse在编译(build)前自动保存源代码文件 一.让Eclipse在编译(build)前自动保存源代码 ...

  3. Linux 下的多线程下载工具 Axel

    Axel 是 Linux 平台下的一款 HTTP/FTP 的高速下载工具,支持多线程以及断点续传,对于一些有速度限制的服务器上下载东西时,Axel 的速度就明显比 wget 要快一些 还有另一个基于 ...

  4. 2019年第十届蓝桥杯省赛-迷宫(BFS/Excel大法)

    这题用dfs搜不出来,需要使用bfs并记录路径,设置好方向顺序跑就ok 正解类似:POJ-3984 迷宫问题 然而毕竟是暴力杯,我们的原则是代码能省就省(懒癌晚期 于是乎网上便出现了形形色色的题解,笔 ...

  5. 网络安全之iptables防火墙

    1>各种传输方式到最后都会转化为能够通过网络发送的数据格式: 1>文本格式: 2>二进制格式:2>TCP三次握手连接,四次断开,连接时客户端是主动打开,服务器是被动 打开,处于 ...

  6. .net Timer定时执行

    System.Timers.Timer可以实现数据库定时更新的功能 Global.asax void Application_Start(object sender, EventArgs e) { / ...

  7. Javascript获取select的选中值和选中文本(转载)

    var obj = document.getElementById(”select_id”); //selectid var index = obj.selectedIndex; // 选中索引 va ...

  8. python 图像识别

    这是一个最简单的图像识别,将图片加载后直接利用Python的一个识别引擎进行识别 将图片中的数字通过 pytesseract.image_to_string(image)识别后将结果存入到本地的txt ...

  9. lua luna工具库

    luna工具库 概述 luna库提供了几个lua开发的常见辅助功能: lua/c++绑定 lua序列化与反序列化 变长整数编码,用于lua序列化,当然也可以方便的用于其他场合 这里把代码编译成了动态库 ...

  10. 爬虫开发10.scrapy框架之日志等级和请求传参

    今日概要 日志等级 请求传参 今日详情 一.Scrapy的日志等级 - 在使用scrapy crawl spiderFileName运行程序时,在终端里打印输出的就是scrapy的日志信息. - 日志 ...