MySQL中分布式事务2PC的异常恢复

分布式事务(也称为 XA 事务)的出现是为了解决分布式环境中跨多个数据库或系统协调事务的复杂性。想象一下,您正在指挥一个管弦乐队,其中每个音乐家代表不同的数据库或服务。就像确保音乐的和谐需要精确的协调一样,维护分布式系统之间的事务完整性也需要仔细的编排。

这就是两阶段提交 (2PC)(XA 事务的一个重要方面)发挥作用的地方。2PC 充当指挥,确保所有音乐家(或数据库参与者)在演奏最终音符之前准备好提交。正如交响乐达到渐强之前每个乐器都必须调准一样,2PC 确保分布式事务的所有组件在继续之前保持同步,从而保证分布式环境中事务的完整性。

典型用例包括针对同一事务使用多个数据库系统的应用程序。在 Java 生态系统中,用例可能是使用 JPA 和 JMS 以及协调分布式事务的 JTA 的企业应用程序(即部署在应用程序服务器上的 EAR)。

如果 MySQL 是参与系统之一,标准流程将是这样:

XA START <xid>;
-- [... SQL Statements ...]
XA END <xid>;
XA PREPARE <xid>;
XA COMMIT <xid>;

<xid> 是事务 ID,由事务协调器(即 JTA)生成的唯一标识符。

当企业应用程序使用带有配置为使用 JTA 的 persistence.xml 的 JPA 时:

<persistence-unit name="samplePU" transaction-type="JTA">
<!-- [...] -->
</persistence-unit>

...SQL 语句被包裹在 XA 事务中。

大多数情况下,上述流程都能完美运行。直到应用程序在 PREPARE 和 COMMIT 之间断开会话。

这时,问题就会发生...

问题症状
事务卡在 "准备"(PREPARED)状态一段时间后仍未被发现。就应用程序而言,数据库无法访问,因此它可能会重试事务并取得成功。

但对 MySQL 而言,事务仍处于准备状态,等待最终裁决:提交或回滚。您可以在 SHOW ENGINE INNODB STATUS 的输出中找到此类事务:

---TRANSACTION 39898344, ACTIVE (PREPARED) 1314869 sec
 4 lock struct(s), heap size 1128, 17 row lock(s), undo log entries 32

哎哟已经超过 15 天了。更糟糕的是,你可能不会随意检查 Innodb 的状态输出:锁会迫使你进行调查。行锁或表锁会无缘无故导致超时。然后你开始搜索,可能会使用

SELECT * FROM information_schema.innodb_trx;

啊哈!确实有一笔事务活跃了那么久!
问题解决了吗?
还没有。

惊喜还在后面:TRX_MYSQL_THREAD_ID 为 0。没有线程在运行这个事务,与正在运行的进程没有关联,也没有任何东西可以 KILL 来清除这个事务。

你决定咬咬牙,重新启动服务器。结果还是一样:事务还在那里等待裁决。虽然这很令人讨厌,但却完全合情合理。

恢复
要提交或回滚事务,只需要事务的 ID。通过发出
XA RECOVER;

该命令的输出对用户并不友好,因此您可能需要试试下面的方法:
XA RECOVER CONVERT XID;

您需要 XA_RECOVER_ADMIN 权限才能执行此命令,否则会收到一条不甚有用的错误信息:

SQL Error [1401] [XAE03]: XAER_RMERR: Fatal error occurred in the transaction branch — check your data for consistency

这样我们就能得到珍贵的十六进制 XID。但 XA COMMIT 或 XA ROLLBACK 仍然无法使用。不管出于什么原因,这两个命令都希望 XID 分成三部分:xid: gtrid [, bqual [, formatID ]]:

  • gtrid 是全局事务标识符,
  • bqual 是分支限定符,
  • formatID 是一个数字,用于标识 gtrid 和 bqual 值所使用的格式。

如语法所示,bqual 和 formatID 是可选项。如果没有给出 bqual,默认值为"'"。如果没有给出,默认 formatID 值为 1。

需要进行一些字符串操作:

  • gtrid:它是 XA RECOVER 报告的 XID 的前 N 个字节,其中 N 在 gtrid_length 列中。
  • bqual:这是 XA RECOVER 报告的 XID 的下一个 M 字节,其中 M 字节位于同一列的 bqual_length 中。
  • formatID:可在 formatID 列中找到。

完成后:

XA COMMIT <gtrid> , <bqual> , <formatID>
-- ...or...
XA ROLLBACK <gtrid> , <bqual> , <formatID>

问题解决了!