(转)MySQL 日志组提交
原文: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 没有保持一致,则会使备库数据丢失造成主备数据不一致。
二阶段提交
如下是二阶段提交流程。
详细执行流程为:
InnoDB 的事务 Prepare 阶段,即 SQL 已经成功执行并生成 redo 和 undo 的内存日志;
binlog 提交,通过 write() 将 binlog 内存日志数据写入文件系统缓存;
fsync() 将 binlog 文件系统缓存日志数据永久写入磁盘;
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 日志组提交的更多相关文章
- 自导自演的面试现场之--你竟然不了解MySQL的组提交?
Hi,大家好!我是白日梦!本文是MySQL专题的第 26 篇. 下文还是白日梦以自导自演的方式,围绕"组提交"展开本话题.看看你能抗到第几问吧 换一种写作风格,自导自演面试现场!感 ...
- MySQL binlog 组提交与 XA(两阶段提交)
1. XA-2PC (two phase commit, 两阶段提交 ) XA是由X/Open组织提出的分布式事务的规范(X代表transaction; A代表accordant?).XA规范主要定义 ...
- MySQL binlog 组提交与 XA(分布式事务、两阶段提交)【转】
概念: XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口.XA为了实现分布 ...
- MySQL binlog 组提交与 XA(两阶段提交)--1
参考了网上几篇比较靠谱的文章 http://www.linuxidc.com/Linux/2015-11/124942.htm http://blog.csdn.net/woqutechteam/ar ...
- mysql复制那点事(2)-binlog组提交源码分析和实现
mysql复制那点事(2)-binlog组提交源码分析和实现 [TOC] 0. 参考文献 序号 文献 1 MySQL 5.7 MTS源码分析 2 MySQL 组提交 3 MySQL Redo/Binl ...
- MySQL Replication--事务组提交和多线程复制
事务组提交和多线程复制 在MySQL 5.7版本引入基于LOGICAL_CLOCK的多线程复制,依赖于BINLOG事件中的last_committed属性,该last_committed属性是否与事务 ...
- mysql并发复制系列 一:binlog组提交
http://blog.itpub.net/28218939/viewspace-1975809/ 作者:沃趣科技MySQL数据库工程师 麻鹏飞 MySQL Binary log在MySQL 5. ...
- innodb二阶段日志提交机制和组提交解析
前些天在查看关于innodb_flush_log_at_trx_commit的官网解释时产生了一些疑问,关于innodb_flush_log_at_trx_commit参数的详细解释参见官网: htt ...
- MySQL崩溃恢复与组提交
Ⅰ.binlog与redo的一致性(原子) 由内部分布式事务保证 我们先来了解下,当一个commit敲下后,内部会发生什么? 步骤 操作 step1 InnoDB做prepare redo log ...
随机推荐
- 网络编程释疑之:单台服务器上的并发TCP连接数可以有多少
曾几何时我们还在寻求网络编程中C10K问题的解决方案,但是现在从硬件和操作系统支持来看单台服务器支持上万并发连接已经没有多少挑战性了.我们先假设单台服务器最多只能支持万级并发连接,其实对绝大多数应用来 ...
- 编写高质量代码改善C#程序的157个建议——建议128:考虑让派生类的名字以基类名字作为后缀
建议128:考虑让派生类的名字以基类名字作为后缀 派生类的名字可以考虑以基类名字作为后缀.这带来的好处是,从类型的名字上我们就知道它包含在哪一个继承体系中. Exception及其子类就是这样一个典型 ...
- 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) [转载]
本系列文章导航 深入浅出Java多线程(1)-方法 join 深入浅出Java多线程(2)-Swing中的EDT(事件分发线程) 深入浅出多线程(3)-Future异步模式以及在JDK1.5Concu ...
- 对于nginx为什么能提高性能
对于后端是动态服务来说,比如Java和PHP.这类服务器(如JBoss和PHP-FPM)的IO处理能力往往不高.Nginx有个好处是它会把Request在读取完整之前buffer住,这样交给后端的就是 ...
- Java如何实现按指定行读取文件
最近在开发实战中,遇到了一个这样的技术情景: 把log4j生成的日志文件定时刷进MySQL数据库,比如三个小时刷一次,那么每次刷数据的时候,如何控制文件读取是从上一次文件读取结束的地方开始继续读取的? ...
- windows游戏开发中一个关于Visual Studio的编译链接成功,输出窗口却显示线程已退出。无法运行项目的问题
可能是显卡驱动程序版本太高了,退回到以前的版本就ok了. 第一次遇见这个问题可把我给整疯了!! 后来又遇到一次,参考之前的解决方法,很快就搞定了!! 可见,经验可是很重要的一个东西啊.
- 搭建服务器集群——Windows7系统中nginx与IIS服务器搭建集群实现负载均衡
转载:https://www.cnblogs.com/xiongze520/p/10308720.html 分布式,集群,云计算机.大数据.负载均衡.高并发······当耳边响起这些词时,做为一个菜鸟 ...
- C#里面获得应用程序的当前路径
在C#里面获得应用程序的当前路径 Environment.CurrentDirectorySystem.IO.Directory.GetCurrentDirectory() ——上面两种获得的是当前路 ...
- CentOS下Docker与.netcore(二) 之 Dockerfile
CentOS下Docker与.netcore(一) 之 安装 CentOS下Docker与.netcore(二) 之 Dockerfile CentOS下Docker与.netcore(三)之 三剑客 ...
- Maven项目编译时报错缺少tools.jar
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default ...