IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

MariaDB与Percona XtraDB的Group Commit实现原理分析

MySQLOPS 数据库与运维自动化技术分享 2012-05-28 13:32:30 累计浏览 2,105 次
本机暂存

MySQL数据库InnoDB存储引擎一直有一个Bug,就是当开启binlog时,无法进行group commit。究其原因,是为了保证InnoDB存储引擎的事务日志与mysqlbinlog日志的顺序一致性。

在prepare前需要获取mutex,直到commit完成之后释放,这也禁用了group commit的功能。

对于MySQL数据库InnoDB存储引擎 group commit的分析,可参考系列文章:MySQL/InnoDB和Group Commit(1)MySQLl/InnoDB和Group Commit(2)

对于MariaDB / PerconaXtraDB如何实现Group commit,可参考MariaDB官方网站的3篇WorkLog:

Percona 5.5.19-rel24代码分析:

以下分析Group Commit的具体实现,基于Percona 5.5.19-rel24。

以下提到的功能,在WorkLog WL#116中都有提及。建议先看WL#116,然后有针对性的看下面的函数实现,绝对事半功倍。

prepare_ordered功能:

Log.cc::MYSQL_BIN_LOG::write_transaction_to_binlog_events(group_commit_entry *entry)

mysql_mutex_tLOCK_group_commit_queue;

事务由mutex保护,加入queue,第一个加入queue的事务负责余下操作;其余事务进入等待。

if (orig_queue != NULL)
entry->thd->wait_for_wakeup_ready();
else
trx_group_commit_leader(entry);
commit_ordered功能:
ha_innodb.cc::innobase_init ->innobase_hton->commit_ordered = innobase_commit_ordered

XA事务总流程:

handler.cc::ha_commit_trans()函数分析
    for (Ha_trx_info *hi = ha_info; hi; hi = hi->next())
        handlerton *ht = hi->ht();
  
        // XA事务第一阶段,group fsync prepare日志
        // 其中,binlog的prepare方法为空实现
        err = ht->prepare();
        need_commit_ordered |= (ht->commit_ordered != NULL);
  
    // Binlog group commit,然后调用commit_ordered进行排序
    cookie = tc_log->log_and_order();
    // XA事务第二阶段,fsync commit日志
    error = commit_one_phase_low();
  
TC_LOG_BINLOG::log_and_order()函数分析
    binlog_commit_flush_stmt/trx_cache();
        binlog_flush_cache();
            write_transaction_to_binlog();
  
                // 事务首先进入binlog group commit queue
                // 第一个进入的事务进行binlog group commit
                // 其余事务进入等待
  
                write_transaction_to_binlog_events();
                    trx_group_commit_leader(); (entry->thd->wait_for_wakeup_ready())
  
                        
  
                        // binlog完成fsync之后,调用此函数对事物进行排序
                        // 排序前,需要获取LOCK_commit_orderedmutex
  
                        run_commit_ordered();
                            for ( … )
                                ht->commit_ordered();
                                // InnoDB提供了此函数,实现排序功能
                                innobase_commit_ordered();
  
Log.cc::MYSQL_BIN_LOG::trx_group_commit_leader()函数分析
  
    // 获取当前queue,并且重新开启一个queue
    // 本queue中的binlog,都由当前事务group commit
  
    // 新queue中的binlog,由新queue中的第一个事务等待LOCK_logmutex
    mysql_mutex_lock(&LOCK_log);
    mysql_mutex_lock(&LOCK_group_commit_queue);
    current = group_commit_queue;
    group_commit_queue = NULL;
    mysql_mutex_unlock(&LOCK_group_commit_queue);
  
    // 写binlog,最后执行一次fsync操作
  
    for ( … )
        write_transaction();
    flush_and_sync();
  
    // 获取commit_orderedmutex,然后才能释放LOCK_logmutex
    // 保证下一个queue,不会在当前queue之前调用commit_ordered
  
    mysql_mutex_lock(&LOCK_commit_ordered);
    mysql_mutex_unlock(&LOCK_log);
  
    // 按照prepare的顺序,调用引擎提供的commit_ordered方法
    // 由于是按序逐个执行commit_ordered方法,因此能够保证事务
    // commit的顺序与binlog commit的顺序是完全一致的
    // 在commit_ordered方法调用完成之后,才能唤醒对应的线程
    // 事务线程被唤醒之后,才能够进入2PC的第二阶段
    // 返回handler.cc::ha_commit_trans()函数,执行commit_one_phase_low
  
    current = queue;
    while (current != NULL)
        run_commit_ordered(current->thd, current->all);
            if (ht->commit_ordered)
               ht->commit_ordered(ht, thd, all);
        current->thd->signal_wakeup_ready();
        current = next;
  
    // 最后释放commit ordered mutex
  
    mysql_mutex_unlock(&LOCK_commit_ordered);
  
ha_innodb.cc::innobase_commit_ordered ->innobase_commit_ordered_low函数分析
    // 获取事务对应的binlog日志的位置
    mysql_bin_log_commit_pos();
    // 设置当前事务标识为flush log later,写commit日志,但是不flush
    trx->flush_log_later = TRUE;
    innobase_commit_low(trx);
    trx->flush_log_later = FALSE;

在函数ha_innodb.cc::innobase_commit_ordered执行完成之后,逐层返回到handler.cc::ha_commit_trans()函数,执行2PC的第二阶段,commit_one_phase_low(),对Commit日志进行group commit。

WL#132

  • 二进制日志Binlog在MySQL数据库中起着TC(Transaction Coordinator)功能二进制日志(Binlog只是TC的一种方案,另一种是Mmap TC);
  • TC在crash恢复时作为控制中心,决定各引擎参与事务的提交与回滚(在WL#164之前,binlog只能与innodb-flush-log-at-trx-commit = 1设置同时使用,二进制日志binlog中的事务一定commit;不存在与binlog中的事务一定rollback;不存在re-play binlog的操作);
  • MySQL数据库为TC做了所谓“middle engine”XA优化(在参与XA事务的所有参与者中,有且仅有一个参与者可以将【prepare,commit】组合简化为commit即可,但是前提是此参与者的commit要在其他参与者完成prepare,未进行commit操作时进行,middle engine),Binlog不需要prepare阶段,其他引擎prepare之后直接写binlog即可;
  • 二进制日志Binlog新增log_and_order方法,控制事务提交顺序;目前,InnoDB存储引擎实现了commit_ordered方法,未实现prepare_ordered方法。

WL#164

在Group Commit的基础上,MariaDB数据库再接再厉,推出WL#164。将一次事务提交需要的3次fsync,降低为1次- binlog fsync。用户可以在将参数innodb-flush-log-at-trx-commit设置为{0,2}时,达到与该参数为1时同样的可靠性,不会丢失已提交更新。实现方案也较为简洁,在原有XA recover的基础上,新增了一个处理【二进制日志binlog存在,但是InnoDB prepare log不存在】的情况,此时需要根据binlog重做(re-play)一遍即可(类似于slave根据binlog恢复的情形)。

在WL#164之后,binlog与InnoDB redo log之间的关系,存在以下几种组合:

  • Binlog与commit log同时存在 ——》no operation in crash recovery
  • Binlog与prepare log同时存在 ——》commit
  • Binlog存在,prepare log不存在 ——》re-play binlog
  • Binlog不存在,prepare log存在 ——》rollback

MySQL数据库外部XA事务支持,语法见上面的博客。

经过测试,

  • 在调用xa prepare命令时,同样不写binlog,或者说是binlog的prepare为空。但是会写InnoDB存储引擎的prepare log。
  • 在调用xa commit命令时,会写binlog,同时写InnoDB存储引擎的commit log。
  • 问题?二进制日志binlog不写prepare日志,如何恢复?

同分类推荐文章

  1. 使用deepseek进行Oracle恢复,引起重大故障 (2026-06-22 10:56:00)
  2. 接手一个只差临门一脚的数据库恢复 (2026-06-18 00:13:09)
  3. 我做了一个 AI 版的 StarRocks 升级风险扫描工具,直接帮我定位到一个风险 (2026-06-15 01:00:00)

查看更多 数据库 文章 →

建议继续学习

  1. 深入浅出INNODB MVCC机制与原理 (累计阅读 9,693)
  2. Innodb IO优化-配置优化 (累计阅读 7,726)
  3. Innodb分表太多或者表分区太多,会导致内存耗尽而宕机 (累计阅读 7,717)
  4. 由浅入深理解索引的实现(2) (累计阅读 7,708)
  5. MySQL中like语句及相关优化器tips (累计阅读 6,281)
  6. Innodb 表和索引结构 (累计阅读 6,224)
  7. InnODB和MyISAM索引统计集合 (累计阅读 6,234)
  8. 从load data引发的死锁说起 (累计阅读 6,142)
  9. 一次神奇的MySQL优化 (累计阅读 6,082)
  10. InnoDB线程并发检查机制 (累计阅读 5,795)