极客时间已完结课程限时免费阅读

加餐 | MySQL XA是如何实现分布式事务的?

加餐 | MySQL XA是如何实现分布式事务的?-极客时间

加餐 | MySQL XA是如何实现分布式事务的?

讲述:于航

时长10:07大小9.27M

你好,我是韩健。
相信很多同学都知道 MySQL 支持单机事务,那么在分布式系统中,涉及多个节点,MySQL 又是怎么实现分布式事务的呢?
这个和我最近遇到的问题很类似,我现在负责的一个业务系统,需要接收来自外部的指令,然后访问多个内部其他系统来执行指令,但执行完指令后,我需要同时更新多个内部 MySQL 数据库中的值(比如 MySQL 数据库 A、B、C)。
但又因为业务敏感,系统必须处于一个一致性状态(也就是说,MySQL 数据库 A、B、C 中的值要么同时更新成功,要么全部不更新)。不然的话,会出现有的系统显示指令执行成功了,有的系统显示指令尚未被执行,导致多部门对指令执行结果理解混乱。
那么我当时是如何实现多个 MySQL 数据库更新的一致性呢?答案就是采用 MySQL XA。
在我看来,MySQL 通过支持 XA 规范的二阶段提交协议,不仅实现了多个 MySQL 数据库操作的事务,还能实现 MySQL、Oracle、SQL Server 等支持 XA 规范的数据库操作的事务。
对你来说,理解 MySQL XA,不仅能理解数据层分布式事务的原理,还能在实际系统中更加深刻的理解二阶段提交协议,这样一来,当你在实际工作中,遇到多个 MySQL 数据库的事务需求时,就知道如何通过 MySQL XA 来处理了。
老规矩,咱们先来看一道思考题。
假设两个数据库 A、B(位于不同的服务器节点上),我们需要实现多个数据库更新(比如,UPDATE executed_table SET status = true WHERE id=100)和插入操作(比如,INSERT into operation_table SET id = 100, op = ‘get-cdn-log’)的事务,那么在 MySQL 中如何实现呢?
带着这个问题,我们进入今天的学习。不过因为 MySQL 通过 XA 规范实现分布式事务的,所以你有必要先来了解一下 XA 规范。

什么是 XA?

提到 XA 规范,就不得不说 DTP 模型( Distributed Transaction Processing),因为 XA 规范约定的是 DTP 模型中 2 个模块(事务管理器和资源管理器)的通讯方式,那 DTP,就是分布式事务处理,就像下图的样子:
为了帮助你更好的理解 DTP 模型,我来解释一下 DTP 各模块的作用。
AP:应用程序(Aplication Program),一般指事务的发起者(比如数据库客户端或者访问数据库的程序),定义事务对应的操作(比如更新操作 UPDATE executed_table SET status = true WHERE id=100)。
RM:资源管理器(Resource Manager),管理共享资源,并提供访问接口,供外部程序来访问共享资源,比如数据库,另外 RM 还应该具有事务提交或回滚的能力。
TM:事务管理器(Transaction Manager),TM 是分布式事务的协调者。TM 与每个 RM 进行通信,协调并完成事务的处理。
你是不是觉得这个架构看起来很复杂?其实在我看来,你可以这么理解这个架构:应用程序访问、使用资源管理器的资源,并通过事务管理器的事务接口(TX interface)定义需要执行的事务操作,然后事务管理器和资源管理器会基于 XA 规范,执行二阶段提交协议。
那么,XA 规范是什么样子的呢?它约定了事务管理器和资源管理器之间双向通讯的接口规范,并实现了二阶段提交协议:
为了帮你更好地理解这个过程,咱们一起走下流程,加深下印象:
AP(应用程序)联系 TM(事务管理器)发起全局事务;
TM 调用 ax_open() 建立与资源管理器的会话;
TM 调用 xa_start() 标记事务分支(Transaction branch)的开头;
AP 访问 RM(资源管理器),并定义具体事务分支的操作,比如更新一条数据记录(UPDATE executed_table SET status = true WHERE id=100)和插入一条数据记录(INSERT into operation_table SET id = 100, op = ‘get-cdn-log’);
TM 调用 xa_end() 标记事务分支的结尾;
TM 调用 xa_prepare() 通知 RM 做好事务分支提交的准备工作,比如锁定相关资源,也就是执行二阶段提交协议的提交请求阶段;
TM 调用 xa_commit() 通知 RM 提交事务分支(xa_rollback() 通知 RM 回滚事务),也就是执行二阶段提交协议的提交执行阶段;
TM 调用 xa_close() 关闭与 RM 的会话。
整个过程,也许有些复杂,不过你可以这么理解:xa_start() 和 xa_end() 在准备和标记事务分支的内容,然后调用 xa_prepare() 和 xa_commit()(或者 xa_rollback())执行二阶段提交协议,实现操作的原子性。在这里需要你注意的是,这些接口需要按照一定顺序执行,比如 xa_start() 必须要在 xa_end() 之前执行。
另外我想说的是,事务管理器对资源管理器调用的 xa_start() 和 xa_end() 这对组合,一般用于标记事务分支(就像上面的更新一条数据记录和插入一条数据记录)的开头和结尾。在这里,你需要注意的是:
对于同一个资源管理器,根据全局事务的要求,可以前后执行多个操作组合,比如,先标记一个插入操作,然后再标记一个更新操作。
事务管理器只是标记事务,并不执行事务,最终是由应用程序通知资源管理器来执行事务操作的。
另外,XA 规范还约定了如何向事务管理器注册和取消资源管理器的 API 接口(也就是 ax_reg() 和 ax_unreg() 接口)。在这里需要你注意的是,这两个接口是 ax_ 开头的,而不是像 xa_start() 那样是 xa_ 开头的,这是很容易误解的点,我希望你能注意到。
那么讲了这么多,我们该如何通过 MySQL XA 实现分布式事务呢?

如何通过 MySQL XA 实现分布式事务呢?

首先,你需要创建一个唯一的事务 ID(比如 xid),来唯一标识事务,并调用“XA START”和“XA END”来定义事务分支对应的操作(比如 INSERT into operation_table SET id = 100, op = ‘get-cdn-log’)。
接着,你需要执行“XA PREPARE”命令,来执行二阶段提交协议的提交请求阶段。
最后,你需要调用“XA COMMIT”来提交事务(或者“XA ROLLBACK”回滚事务)。这样你就实现了全局事务的一致性了。
从上面的流程中,你可以看到,客户端在扮演事务管理器的角色,而 MySQL 数据库就在扮演资源管理器的角色。另外,你要注意,上面流程中的 xid 必须是唯一值。
另外,我想补充的是,如果你要开启 MySQL 的 XA 功能,必须设置存储引擎为 InnoDB,也就是说,在 MySQL 中,只有 InnoDB 引擎支持 XA 规范。
当然了,可能有些同学对 MySQL XA 有这样的疑问,能否将“XA END”和“XA PREPARE”合并到一起呢?答案是不能,因为在“XA END”之后,是可以直接执行“XA COMMIT”的,也就是一阶段提交(比如当共享资源变更只涉及到一个 RM 时)。
最后,我强调一下,MySQL XA 性能不高,适合在并发性能要求不高的场景中使用,而我之所以需要采用 MySQL XA 实现分布式事务,不仅因为整个系统对并发性能要求不高,还因为底层架构是多个第三方的,没法改造。

内容小结

本节课我主要带你了解了 XA 规范,以及如何使用 MySQL XA 实现分布式事务。我希望你明确这样几个重点。
1.XA 规范是个标准的规范,也就是说,无论是否是相同的数据库,只要这些数据库(比如 MySQL、Oracle、SQL Server)支持 XA 规范,那么它们就能实现分布式事务,也就是能保证全局事务的一致性。
2. 相比商业数据库对 XA 规范的支持,MySQL XA 性能不高,所以,我不推荐你在高并发的性能至上场景中,使用 MySQL XA。
3. 在实际开发中,为了降低单点压力,通常会根据业务情况进行分表分库,将表分布在不同的库中,那么,在这种情况下,如果后续需要保证全局事务的一致性时,也需要实现分布式事务。
最后我想说的是,尽管 XA 规范保证了全局事务的一致性,实现成本较低,而且包括 MySQL 在内的主流数据库都已经支持,但因为 XA 规范是基于二阶段提交协议实现的,所以它也存在二阶段提交协议的局限,比如:
首先,XA 规范存在单点问题,也就是说,因为事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成了,在第二阶段正准备提交的时候,事务管理器宕机了,相关的资源会被锁定,无法访问。
其次,XA 规范存在资源锁定的问题,也就是说,在进入准备阶段后,资源管理器中的资源将处于锁定状态,直到提交完成或者回滚完成。
不过,虽然 MySQL XA 能实现数据层的分布式事务,但在我现在负责的这套业务系统中,还面临这样的问题:那就是在接收到外部的指令后,我需要访问多个内部系统,执行指令约定的操作,而且,我必须保证指令执行的原子性,也就是说,要么全部成功,要么全部失败,那么我应该怎么做呢?答案是 TCC,这也是我在下一讲想和你聊一聊的。

课堂思考

既然我提到了我通过 MySQL XA 解决了数据库操作的一致性问题,而 MySQL XA 性能不高,适用于对并发性能要求不高的场景中。那么你不妨想想,在 MySQL XA 不能满足并发需求时,如何重新设计底层数据系统,来避免采用分布式事务呢?为什么呢?欢迎在留言区分享你的看法,与我一同讨论。
最后,感谢你的阅读,如果这节课让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 11

提建议

上一篇
加餐 | ZAB协议(三):如何处理读写请求?
下一篇
加餐 | TCC如何实现指令执行的原子性?
 写留言

精选留言(14)

  • 侧耳倾听
    2020-06-23
    既然是多数据库场景,首先,需要搞清楚的是为什么引入多数据库?如果是基于性能的考虑,并发量大,需要考虑的是读多写少,还是写多读少?如果是读多写少,那么可以采用一主多从,主库负责写,以及引入缓存机制来提高数据的实时性。如果是写多读少,问题有些复杂,如果读的实时要求不高,可以考虑采用队列的形式,后台线程负责写入数据库。如果是基于业务的拆分,要搞明白是不是需要拆分?是在现有硬件基础上解决不了并发的写入,还是仅仅是因为模仿。如果是后者,不妨先考虑将业务表的表空间分不到不同的磁盘,避免单磁盘的写入,可以提高一定的数据写入效率。在这个过程中还需要考虑数据库各种缓存的大小设定,比如chang buffer,redolog文件。争取做到单点优化做到无突破,再考虑横向或者纵向的扩展。一旦牵扯进分布式架构,就不再是一个数量级的问题展现。
    展开

    作者回复: 加一颗星:)

    31
  • ppyh
    2020-06-26
    这个和我最近遇到的问题很类似,我现在负责的一个业务系统,需要接收来自外部的指令,然后访问多个内部其他系统来执行指令,但执行完指令后,我需要同时更新多个内部 MySQL 数据库中的值(比如 MySQL 数据库 A、B、C)。 不过,虽然 MySQL XA 能实现数据层的分布式事务,但在我现在负责的这套业务系统中,还面临这样的问题:那就是在接收到外部的指令后,我需要访问多个内部系统,执行指令约定的操作,而且,我必须保证指令执行的原子性,也就是说,要么全部成功,要么全部失败,那么我应该怎么做呢?答案是 TCC,这也是我在下一讲想和你聊一聊的。 这两句话有区别吗,怎么第一句话就可以用mysql xa,第二句话就得用tcc啊
    展开

    作者回复: 加一颗星:),第一个问题是如何实现多个MySQL间的操作事务,第二问题,是实现多个业务系统(包括非数据库系统)的操作事务,这时要考虑TCC了。

    共 3 条评论
    20
  • hello
    2020-06-23
    课程都完结好长一段时间了,老师还在时不时加餐,给老师点赞。

    作者回复: 感谢一路相伴,多交流:)

    9
  • 槑·先生
    2020-06-22
    老师,有个问题不太理解。事务不就包含原子性吗?分布式事务不是也应该包含指令的原子性吗,为什么会存在最后那个指令原子性的问题。

    作者回复: 加一颗星:),MySQL XA实现的是数据库层面的分布式事务,那么,当不仅涉及到数据库,还涉及到其他业务系统时,这时就需要TCC了。

    共 2 条评论
    3
  • janey
    2022-03-11
    请问老师,mysql xa在xa prepare后资源锁定,锁定的主要是什么资源呢?这个资源锁定的影响面主要是哪些?
    3
  • ξ!
    2020-06-23
    老师,raft是不是在保证有状态节点间的内存中数据一致性,而XA在保证数据持久化后的一致性

    作者回复: 加一颗星:),XA实现的是分布式事务,系统状态的一致性,操作要么全部执行成功,要么全部不执行。Raft能保证的是,写操作完成后,后续所有的读操作都能读取到更新后的值。

    共 4 条评论
    2
  • Andy Huang
    2022-04-23
    有谁知道postgresql的分布式版本有哪些? 各是用了什么协议? 我正在做选型
  • ezekiel
    2021-10-10
    既然我提到了我通过 MySQL XA 解决了数据库操作的一致性问题,而 MySQL XA 性能不高,适用于对并发性能要求不高的场景中。那么你不妨想想,在 MySQL XA 不能满足并发需求时,如何重新设计底层数据系统,来避免采用分布式事务呢?为什么呢? ============================================================== 思考题的答案是什么呢?
    共 1 条评论
  • Geek_a21638
    2021-10-09
    "事务管理器只是标记事务,并不执行事务,最终是由(应用程序)通知资源管理器来执行事务操作的", 我的理解应该是 “最终是由(事务管理器TM)通知资源管理器来执行事务操作的”??
  • blentle
    2021-02-05
    韩老师, 如果有一个事物分支在 commit阶段由于外部影响,commit了一个不存在的 Xid. 抛出了异常.这时其他的分支已经commit成功了. 最终能否保证所有的事物分支回滚呢?
  • 路人
    2020-09-03
    commit阶段如果只有部分成功,需要程序考虑补偿机制
    共 2 条评论
  • Heaven
    2020-08-20
    我的思路是,首先只在主节点上执行事务,然后降低和跟随者的延迟,来保证,在主节点上执行成功了,必然会可以在从节点上执行成功,这是否是一种解决方案呢? 可以利用GTID来辅助查询工作

    作者回复: 加一颗星:),这里的场景是执行多条SQL语句,在不同节点上执行不同的语句,需要实现各节点状态的一致,要么全部成功,要么全部失败。

  • 扩散性百万咸面包
    2020-08-19
    按照文章中的表述,如果要实现分布式事务的话,需要对应的数据库实现XA规范接口。而且客户端会聚集一定的复杂性:因为我看图中客户端需要把一个事务里的不同语句发给不同的MySQL,那要怎么样才能知道自己的语句应该放在哪个SQL上执行呢?

    作者回复: 加一颗星:),不同的连接。

  • Geek_9d0e04
    2020-07-19
    请假老师,操作两个数据库,我自己在代码中也能实现两阶段提交呀,两个数据库连接先各自执行自己的SQL,作为第一阶段;都没问题再提交事务,否则回滚,作为第二阶段。我觉得事务管理器的作用,也就是上面内容吧,那为何有需要独立一个事务管理器,增加单点呢?

    作者回复: 加一颗星:),事务管理器是逻辑的,一般不需要独立的事务管理器。