1. 引言

分布式事务主要应用领域主要体现在数据库领域、微服务应用领域。微服务应用领域一般是柔性事务,不完全满足ACID特性,特别是I隔离性,比如说saga不满足隔离性,主要是通过根据分支事务执行成功或失败,执行相应的前滚的重试或者后滚的补偿操作来达成全局事务的最终一致性,但是全局事务与全局事务之间没有隔离性。

笔者了解到的分布式事务方案有2PC的XA规范,以及Google 的percolator方案(TiDB就采用这个实现,本质上是基于全局时间戳的乐观锁版本校验)。

mysql的XA应用场景分为外部XA与内部XA,内部XA用于binlog与stroage engine之间,协调binlog与redo事务写入的原子性。外部XA用于mysql节点与mysql节点之间,协调跨物理库之间的原子性。本文主要介绍外部XA。

基于mysql的XA两阶段事务提交(2PC)分布式事务,需要一个事务协调器(Transaction Manager)来接受应用提交的全局事务(Global Transaction),全局事务经过TM的分解后,分解成多个分支事务(Branch Transaction),每个分支事务在具体的某个mysql实例上运行,其中mysql作为资源管理器(Resource Manager)。

在实际的分布式数据库的分布式事务的开发中,一般选择DBProxy作为TM载体,比如腾讯的TDSQL和阿里的POLARDB-X的分布式事务方案,都是这样的实现。

XA的2PC提交流程的主要处理逻辑在事务协调器(Transaction Manager),一般选择DBProxy作为TM载体,如果DBProxy用Java开发,可以参考Atomikos的实现

2. XA协作流程

图2.1 XA的2PC协作流程

XA的2PC提交流程如图2.1,主要分为以下几个步骤。

1) App发送start global transaction到TM,TM生成全局事务ID,xid

2) App发送global transaction语句到TM,TM根据具体的Sharding算法分解出 branch transaction,并且发送到各个mysql节点。

3) App发送commit语句到TM,TM往各个branch transaction的mysql节点发送XA prepare‘xid’语句。

4) TM收集各个Prepare语句的响应,如果各个响应都是OK,则向每个branch transaction的mysql节点发送XA commit ‘xid’语句,如果各个RM响应有不OK的,往每个RM上发送XA rollback‘xid’语句。

3. XA优化与异常处理

优化1:持久化事务协调阶段的各个状态

TM作为一个单点的事务协同器,很有可能宕机,出现单点故障。其本身的职责主要是事务协调,属于无状态的服务。宕机重启后,可以根据持久化的全局事务状态来恢复TM的执行逻辑,所以,需要将阶段的各个协调阶段以及该阶段中每个RM的执行状态持久化到独立的DB中,多个TM共享一个持久化DB。具体的阶段有,prepare阶段的子阶段有branch_tansaction_ send、prepare_send、prepare_ack阶段,commit阶段的子阶段有commit_send、commit_ack阶段,记录每个子阶段每个RM的执行状态。

优化2:并行发送语句

在branch_tansaction_ send、prepare_ send、commit_send阶段,如果TM往RM发送语句是串行执行的,单个global transaction的执行时间加长,TM的TPS(每秒事务请求数)会降低,可以在这些阶段将已生成的语句,通过线程池并行发送到各个RM,TM同时同步等待语句的返回值,延时大为降低。

异常1:TM在prepare_send阶段前宕机,重启恢复后,继续执行prepare_send动作。

异常2:TM在prepare_send阶段时宕机,可能会有部分RM收到prepare语句,部份没有收到,重启后,往收到prepare语句的RM发送rollback语句。

异常3:TM在prepare_ack阶段记录完各个RM的执行状态后宕机,重启后,根据日志状态发起rollback或者commit语句。

异常4:TM在commit_send阶段时宕机,可能会有部分RM收到commit语句,部份没有收到,重启后,往没有收到commit语句的RM发送commit语句。

异常5:TM在commit_ ack阶段记录完各个RM的执行状态后宕机,重启后,根据日志状态发起重试commit语句或者不操作。

异常6:RM超长时间没有收到TM的rollback或者commit语句,一直持有记录锁,RM要有自动rollback或者commit的功能。

4. 2PC与1PC对比

XA的两阶段提交,直观感觉和RM的交互次数太多,RPC次数太多,影响单个全局事务的响应时间,TPS肯定降低。但是,prepare阶段有存在的意义,如果某个单机事务处于prepare状态,一直没有commit,mysql重启时,进行崩溃恢复时,如果binlog中没有该事务,对该事务进行rollback,如果有,则对该事务进行commit。

XA两阶段提交满足了事务的ACID属性,原子性:在prepare和commit阶段保障了事务的原子性。隔离性:通过mysql原生的记录锁,做到读写隔离。持久性:基于mysql单机事务的redo实现了持久性。一致性:基于mysql单机事务。

如果放弃prepare阶段,只有commit阶段,全局事务的原子性无法保障,例如这个场景,全局事务的部分分支事务commit成功,另一部分分支事务commit失败,此时全局事务就处于既不能commit成功,也不能rollback成功,因为已经成功commit的分支事务无法rollback。

即使通过解析binlog,生成反向SQL进行补偿达到rollback的效果,此时也会多产生一次交互,RPC次数和两阶段提交是一样的了。但是此时又引发一个新问题,全局事务的隔离性难以保障,因为另一个全局事务2可能会修改此时全局事务1的已经commit了的记录,而全局事务1正在反向补偿同一条已经commit了的记录。

即使通过以下方法达到了隔离性,只满足Read Commited隔离级别,Repeated Read等隔离级别没有实现,而且隔离的粒度比较大,记录上的Xid,相当于一把记录写锁。

在每个记录上,增加一个字段全局事务ID(Xid),只有满足以下两个条件之一方可访问该记录。

1)记录上Xid是本全局事务的Xid,

2)记录上Xid不是本全局事务ID,且该Xid已经不活跃

总结,TM和各个RM都处于完全正常的情况下,1PC的性能比起2PC会好,尤其是TPS。但是在RM处于异常的场景下,例如全局事务的部分分支事务commit成功,另一部分分支事务commit失败。1PC的TPS可能跟2PC差不多。

5. XA各个阶段的Mysql处理流程

上图为XA规范图,规范中xa_open与xa_close不会频繁调用,TM与RM要维持数据库长连接,避免频繁的创建、销毁数据库连接的开销。

上图5.2为mysql内部Xa的流程图。

xa_start与xa_end起到标识分支事务的作用,具体由mysql服务端Sql_cmd_xa_start::trans_xa_start()函数与Sql_cmd_xa_end::trans_xa_end()函数实现

Sql_cmd_xa_start::trans_xa_start() 把thd->get_transaction()->xid_state设置为XID_STATE::XA_ACTIVE状态

Sql_cmd_xa_end::trans_xa_end() 检查thd->get_transaction()->xid_state必须为XID_STATE::XA_ACTIVE状态

6. mysql源码跟踪

xa_prepare内部函数调用流程

mysql_execute_command()
case
SQLCOM_XA_PREPARE:
   res=
lex->m_sql_cmd->execute(thd);

    Sql_cmd_xa_prepare::execute(THD
*thd)

      Sql_cmd_xa_prepare::trans_xa_prepare(THD
*thd)

        ha_prepare(THD
*thd)

          innobase_xa_prepare

            trx_prepare_for_mysql(trx_t*
trx)

               trx_prepare()

                  trx_prepare_low()

                    trx_undo_set_state_at_prepare()
修改undolog状态为prepare状态

                      mlog_write_ulint()
写redo
buffer

                        mtr_commit(&mtr)将redo
buffer写入redo
log file,并将脏页挂载在buffer
pool的flushlist,可以看出写undo
segment也需要redo保护

xa_commit内部流程

mysql_execute_command()

case SQLCOM_XA_COMMIT:

res= lex->m_sql_cmd->execute(thd);

  Sql_cmd_xa_commit::execute(THD *thd)

    Sql_cmd_xa_commit::trans_xa_commit(THD *thd)

      MYSQL_BIN_LOG::commit

        ha_commit_low

          innobase_commit

            innobase_commit_low

               trx_commit_for_mysql()

                 trx_commit()

                   trx_commit_low()

                    trx_commit_in_memory()

                    lock_trx_release_locks() 释放事务的记录锁

                        trx_flush_log_if_needed() 刷新redo buffer到redo log

                          log_write_up_to(lsn, flush);

                             log_write_flush_to_disk_low() 具体刷盘动作

分支事务update处理流程

mysql_execute_command()

  case SQLCOM_UPDATE:

    res= lex->m_sql_cmd->execute(thd);

      Sql_cmd_update::execute(THD *thd)

        try_single_table_update

          open_tables_for_query(THD *thd, TABLE_LIST *tables, uint flags)

          open_and_process_table

              open_table()

           mysql_update

              table->init_cost_model()

              ha_innobase::info

                ha_innobase::info_low获取统计信息

               test_quick_select()根据代价模型,获取开销最低的表访问方式,如range\table scan\index scan

                ha_innobase::try_semi_consistent_read(true),请求存储引擎开启半一致性读,在update 或者delete的语句中。

                init_read_record设置数据扫描方法,如rr_quickrr_sequential

                  handler::ha_rnd_init

                    ha_innobase::rnd_init,初始化c

                rr_sequential

                  handler::ha_rnd_next扫描一条记录

                    ha_innobase::rnd_next() table scan读取第一条记录

                      row_search_mvcc()

                         sel_set_rec_lock() 在一条记录上加锁

                           lock_clust_rec_read_check_and_lock在聚集索引上加记录锁

                             lock_rec_lock加记录锁

                handler::ha_update_row

                  binlog_log_row

                    THD::binlog_update_row记录row格式的binlog

                  ha_innobase::update_row(old_row,new_row)

                  row_upd_clust_rec()
更新聚集索引记录

                    trx_undo_report_row_operation()
记录undo信息

                      trx_undo_assign_undo()
分配回滚段

                      trx_undo_page_report_modify()
在回滚段中记录聚集索引的更改

                  row_upd_rec_in_place()
更新操作写入聚集索引

                row_upd_rec_in_place_log()更新操作写入redo
buffer

                  mtr_t::commit()
将redo
buffer写入redo日志文件,并将脏页挂载在buffer
pool的flushlist

数据库分布式事务XA规范介绍及Mysql底层实现机制的更多相关文章

  1. MySQL数据库分布式事务XA优缺点与改进方案

    1 MySQL 外部XA分析 1.1 作用分析 MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,例如开源的代理工具:ameoba[4],网易的DDB,淘宝的 ...

  2. DTP模型之一:(XA协议之三)MySQL数据库分布式事务XA优缺点与改进方案

    1 MySQL 外部XA分析 1.1 作用分析 MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,例如开源的代理工具:ameoba[4],网易的DDB,淘宝的 ...

  3. Mysql数据库分布式事务XA详解

    XA事务简介 XA 事务的基础是两阶段提交协议.需要有一个事务协调者来保证所有的事务参与者都完成了准备工作(第一阶段).如果协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段 ...

  4. 详解Mysql分布式事务XA(跨数据库事务)

    详解Mysql分布式事务XA(跨数据库事务) 学习了:http://blog.csdn.net/soonfly/article/details/70677138 mysql执行XA事物的时候,mysq ...

  5. 一文教你迅速解决分布式事务 XA 一致性问题

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯云数据库团队 近日,腾讯云发布了分布式数据库解决方案(DCDB),其最明显的特性之一就是提供了高于开源分布式事务XA的性能.大型 ...

  6. 分布式事务XA

    1.什么是分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上.以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成 ...

  7. 分布式事务 XA 两段式事务 X/open CAP BASE 一次分清

    分布式事务: 分布式事务是处理多节点上 的数据保持 类似传统 ACID 事物特性的 一种事物. XA:是一种协议,一种分布式事务的协议,核心思想是2段式提交. 1 准备阶段  2 提交阶段.XA协议是 ...

  8. MySQL 中基于 XA 实现的分布式事务

    1 XA协议 首先我们来简要看下分布式事务处理的XA规范可知XA规范中分布式事务有AP,RM,TM组成: 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务 ...

  9. 分布式事务、XA、两阶段提交、一阶段提交

    本文原文连接:http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的分布式事务的规范 ...

随机推荐

  1. 【案例演示】JVM之强引用、软引用、弱引用、虚引用

    1.背景 想要理解对象什么时候回收,就要理解到对象引用这个概念,于是有了下文 2.java中引用对象结构图 3.引用详解 3.1.什么是强引用 a.当内存不足,JVM开始垃圾回收,对于强引用的对象,就 ...

  2. 自由切换 网页上的 ico 图标

    自由切换 网页上的   ico   图标: 第一步:      进入这个网站 :https://www.uupoop.com/ico/?action=make 第二步:      进入网站后,然后选择 ...

  3. JavaWeb网上图书商城完整项目-数据库操作工具类2-MapHandle的高级用法

    1.现在在上面一章的基础上,我们引入一个address表,该表记录person类的地址,address表的格式如下所示 现在person类要和address表想关联,得到当前联系人的住宅地址,我们应该 ...

  4. ssh -i 密钥文件无法登陆问题

    一.用ssh 带密钥文件登录时候,发生以下报错 [root@99cloud1 ~]# ssh -i hz-keypair-demo.pem centos@172.16.17.104The authen ...

  5. 2020年学习目标之一——emacs

    这两天在虚机里面安装了centos7(gnome),决定后续自己的学习一直在这个里面进行,对于编辑器我最后选择了emacs,新手一枚,不过正好也算是今年的一项学习目标吧,加油! (完)

  6. int c, int ndigit[10]; ++ndigit[c-'0'];

    for example c-'0' is an integer expression with a value between 0and 9 corresponding to the characte ...

  7. 附007.Docker全系列大总结

    Docker全系列总结如下,后期不定期更新. 欢迎基于学习.交流目的的转载和分享,禁止任何商业盗用,同时希望能带上原文出处,尊重ITer的成果,也是尊重知识. 若发现任何错误或纰漏,留言反馈或右侧添加 ...

  8. WIN10有线网络反复断开解决方法

    最近家里台式机碰到一个奇怪的问题,开机之后有线网络就时断时续,右下角网络图标不停在小地球与小电脑之间切换.网上大概搜索了一下,貌似碰到这种问题的朋友不在少数,但大部分朋友碰到的都是无线网络居多.这里把 ...

  9. scrapy爬取海量数据并保存在MongoDB和MySQL数据库中

    前言 一般我们都会将数据爬取下来保存在临时文件或者控制台直接输出,但对于超大规模数据的快速读写,高并发场景的访问,用数据库管理无疑是不二之选.首先简单描述一下MySQL和MongoDB的区别:MySQ ...

  10. 【UWP】利用EF Core操作SQLite

    在以往开发中,一定要在vs中安装SQLite for Universal App Platform以及一款wrapper,如SQLitePCL.现在有了EntitfyFramewrok Core,我们 ...