苹果公司|PolarDB-X 源码解读:事务的一生( 五 )


至此 , 我们看到了例 1 事务的一生 。 接下来看一下多分片写的事务流程 。
例 2 事务的一生 写分片 0
例 2 中 , 从开启事务到执行 UPDATE tb1 SET a = 100 WHERE id = 1 走的流程和例 1 一样 , 直到执行 UPDATE tb1 SET a = 100 WHERE id = 0 时 , 才有所不同 。 具体而言 , 事务在之前就获取了分片 0 的只读连接 , 当执行到 TransactionConnectionHolder#getConnection 时 , 会先提交只读连接上的事务(实际执行了 ROLLBACK) , 然后在这条连接上用 XA START 开启 XA 事务 , 设置好时间戳 , 就可以把这个物理连接返回给执行器 , 最终执行物理 SQL 。 由于这是第二个写连接 , 因此还会设置事务为跨分片事务 , 以触发正常的两阶段提交流程 。
COMMIT
例 2 的重点在于写了 2 个分片 , 因此 COMMIT 时会调用 commitMultiShardTrx() 走多分片的分布式提交流程 。 方法 commitMultiShardTrx 代码如下:
// TsoTransaction#commitMultiShardTrx()protected void commitMultiShardTrx() { // 事务日志的提交状态 , 一共有 3 种:FAILURE , UNKNODWN , SUCCESS 。TransactionCommitState commitState = TransactionCommitState.FAILURE; try { // 对所有连接执行 XA prepare 。prepareConnections(); // 拿 commit 时间戳 。commitTimestamp = nextTimestamp(); Connection logConn = /* 获取主分片上的连接 */; // 所有分支事务 prepare 成功 , 在写事务日志前 , 状态设置为 UNKNOWN , 其作用见后续代码说明 。commitState = TransactionCommitState.UNKNOWN; // 写事务日志 。writeCommitLog(logConn); // 写事务日志成功 , 状态置为 SUCCESS 。commitState = TransactionCommitState.SUCCESS;catch (RuntimeException ex) { exception = ex;if (commitState == TransactionCommitState.FAILURE) { // 写事务日志前失败了 , 回滚所有连接 。rollbackConnections();else if (commitState == TransactionCommitState.SUCCESS) { // 写事务日志成功了 , 提交所有连接 。commitConnections();else { // 所有连接都 prepare 成功了 , 但无法确定是否写入了事务日志 ,// 将由事务恢复的逻辑来决定是 COMMIT 还是 ROLLBACK 。 这里先丢弃所有连接 。discardConnections();// 释放并清空这些物理连接 。connectionHolder.closeAllConnections(); // prepare 或 commit 阶段抛出的异常 , 这里重新抛出 。if (exception != null) { throw exception;上述代码正是两阶段提交的流程 。
在 prepare 阶段 , 调用 prepareConnections() , 其中对只读连接只是简单执行 ROLLBACK 语句;对于写连接 , 执行 XA END {xid 和 XA PREPARE {xid 语句 。
如果所有连接都 prepare 成功了 , 在 commit 阶段 , 调用 writeCommitLog(logConn) , 用主分片(在我们的例子中 , 是分片 1)的连接 , 写下事务日志 , 主要包括了事务的 id 和 commit 时间戳 , 事务日志主要用于后续的事务恢复 。
如果事务日志写成功了 , 就意味着 commit 成功了 , 会调用 commitConnections() 对所有连接进行提交 , 这一步只用对写连接设置 commit 时间戳 SET innodb_commit_seq = {commitTimestamp 和执行 XA COMMIT {xid 语句 。
如果在写事务日志前失败了 , 会调用 rollbackConnections() 对所有连接进行回滚 , 主要会对写连接执行 XA ROLLBACK {xid 回滚 。
如果无法确定是否成功写入了事务日志 , 事务的状态会是 UNKNOWN 。 此时 , 我们能确定所有分支事务都成功 prepare 了 , 如果事务日志写入成功了 , 则要进行 XA COMMIT;如果事务日志没有写入 , 则要进行 XA ROLLBACK 。 由于不确定是要提交还是回滚 , 我们会丢弃所有物理连接 , 使这些连接后续不再可用 。 至于事务最终是提交还是回滚 , 则交给事务恢复线程来处理 , 接下来我们会解读事务恢复相关的代码 。
事务恢复 一个分布式事务在提交的时候 , 可能遇到各种情况导致提交失败 。 例如 , 所有分支事务都 prepare 成功了 , 事务日志也写入成功了 , 但 XA COMMIT 失败了 。 对于这种情况 , 事务恢复时需要正确提交所有分支事务 。 另一种情况是 , 部分分支事务 prepare 成功了 , 另外一些失败了 , 或事务日志没有写入成功 。 对于这种情况 , 事务恢复需要回滚掉已 prepare 的分支事务 。

相关经验推荐