(转)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 没有保持一致,则会使备库数据丢失造成主备数据不一致。

二阶段提交

如下是二阶段提交流程。

详细执行流程为:

  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 。

时间: 2024-10-12 23:09:56

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

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

1. XA-2PC (two phase commit, 两阶段提交 ) XA是由X/Open组织提出的分布式事务的规范(X代表transaction; A代表accordant?).XA规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口.XA为了实现分布式事务,将事务的提交分成了两个阶段:也就是2PC (tow phase commit),XA协议就是通过将事务的提交分为两个阶段来实现分布

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

概念: XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口.XA为了实现分布式事务,将事务的提交分成了两个阶段:也就是2PC (tow phase commit),XA协议就是通过将事务的提交分为两个阶段来实现分布式事务. 两阶段: 1)prepare 阶段 事务管理器向所有涉及到的数据库服务器发出prepare"准备提交"请求,数据库收到请求后执行数据修改和日志

MYSQL组提交

组提交(group commit)是MYSQL处理日志的一种优化方式,主要为了解决写日志时频繁刷磁盘的问题.组提交伴随着MYSQL的发展不断优化,从最初只支持redo log 组提交,到目前5.6官方版本同时支持redo log 和binlog组提交.组提交的实现大大提高了mysql的事务处理性能,下文将以innodb 存储引擎为例,详细介绍组提交在各个阶段的实现原理. redo log的组提交 WAL(Write-Ahead-Logging)是实现事务持久性的一个常用技术,基本原理是在提交事务

MySQL崩溃恢复与组提交

Ⅰ.binlog与redo的一致性(原子) 由内部分布式事务保证 我们先来了解下,当一个commit敲下后,内部会发生什么? 步骤 操作 step1 InnoDB做prepare redo log(fsync) step2 Sever层写binlog(fsync) step3 InnoDB层commit redo log(fsync) 第一步写的redo file,写入的是trxid而不是page的变化(show binlog events in 'xxx'),准确的说写在undo页上 第三步写

mysql 5.6 binlog组提交实现原理

mysql 5.6 binlog组提交实现原理 http://blog.itpub.net/15480802/viewspace-1411356 Redo组提交 Redo提交流程大致如下 lock log->mutex write redo log buffer to disk unlock log->mutex fsync Fsync写磁盘耗时较长且不占用log->mutex,也就是其执行期间其他线程可以write log buffer: 假定一次fsync需要10ms,而写buffe

MySQL 日志

MySQL 日志 ================================================================================ 概述: ================================================================================ MySQL的日志分类: ★查询日志:general_log ★慢查询日志:log_slow_queries ★错误日志:log_error, log_

MySQL日志系统

body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-top: 10px; padding-bottom: 10px; background-color: white; padding: 30px } body>*:first-child { margin-top: 0 !important } body>*:last-child { margin-bottom:

MySQL 日志的类型

MySQL日志类别:一般查询日志:log,general_log,log_output慢查询日志:查询执行的时长超过指定的查询,即为慢查询:错误日志:通常时指错误日志的相关信息,通常用服务器关闭和启动的日志信息,服务器运行过程中的错误信息,还可以记录警告信息.二进制日志:只是跟修改相关的操作,可以理解为一个重做日志,用于复制的基本凭据:中继日志:它其实跟复制相关的,与二进制日志几乎相同: 事物日志:随机I/O转换为顺序I/O,一般在两个文件存储,一个存满了就换另外一个存 查询日志 log={ON

Linux命令:MySQL系列之十一--MySQL日志管理

MySQL日志管理 SHOW GLOBAL VARIABLES LIKE '%log%':查看关于log的全局变量 一.日志分类 1.错误日志   2.一般查询日志   3.慢查询日志 4.二进制日志  5.中继日志   6.事务日志   7.滚动日志 二.日志详解 1.错误日志 说明:在对应的数据目录中,以主机名+.err命名的文件,错误日志记录的信息类型: 记录了服务器运行中产生的错误信息 记录了服务在启动和停止是所产生的信息 在从服务器上如果启动了复制进程的时候,复制进程的信息也会被记录