共计 13779 个字符,预计需要花费 35 分钟才能阅读完成。
简介:本文将次要解读 PolarDB-X 中事务局部的相干代码,着重解读事务的毕生在计算节点(CN)中的要害代码:从开始、执行、到最初提交这一整个生命周期。概述本文将次要解读 PolarDB-X 中事务局部的相干代码,着重解读事务的毕生在计算节点(CN)中的要害代码:从开始、执行、到最初提交这一整个生命周期。在浏览本文前,强烈推荐先浏览与 PolarDB-X 事务零碎相干的文章:PolarDB-X 强统一分布式事务原理 PolarDB-X 分布式事务的实现(一)PolarDB-X 分布式事务的实现(二)InnoDB CTS 扩大 PolarDB-X 分布式事务的实现(四):跨地区事务无处不在的 MySQL XA 事务以及此前公布的 PolarDB-X SQL 的毕生事务与连贯在 PolarDB-X 的 CN 层,与事务关系密切的是连贯。这是因为数据节点(DN)也具备单个 DN 内的事务能力,CN 则通过与 DN 的连贯来治理 DN 上的事务,从而实现强统一的分布式事务能力。其中波及到的连贯大抵如下图所示:
先简略说一下这外面波及的一些连贯。ServerConnection 相似于前端连贯,大部分的 SQL 语句执行的入口都是 ServerConnection#innerExecute。TConnection 中的 executeSQL 办法负责 SQL 语句的真正执行,也负责创立新的事务对象。TConnection 会始终援用着这个事务对象,直到事务提交或回滚。事务对象里有一个 TransactionConnectionHolder,负责管理该事务用到的所有物理连贯(CN 连贯 DN 的公有协定连贯)。值得一提的是,ExecutionContext 作为一条逻辑 SQL 执行的上下文,也会援用这个事务对象。这样,后续执行器须要应用物理连贯与 DN 通信时,就能够通过 ExecutionContext 拿到事务对象,再通过事务对象的 TransactionConnectionHolder 拿到适合的物理连贯。以上的各种连贯,都会在下文持续探讨。两个例子接下来,咱们以两个简略的例子,来阐明事务的毕生在 CN 的代码中是如何体现的。测试用表:CREATE TABLE tb1
(
`id` int PRIMARY KEY,
`a` int
) DBPARTITION BY HASH(id
) 先在外面插入几条数据:INSERT INTO tb1 VALUES (0, 0), (1, 1), (2, 2), (3, 3); 测试应用的两个例子:– Example 1:
BEGIN;
SELECT * FROM tb1 WHERE id = 0;
UPDATE tb1 SET a = 100 WHERE id = 1;
COMMIT;– Example 2:
BEGIN;
SELECT * FROM tb1 WHERE id = 0;
UPDATE tb1 SET a = 101 WHERE id = 1;
UPDATE tb1 SET a = 101 WHERE id = 0;
COMMIT; 留神到例 2 只比 例 1 多批改了 id = 1 的数据。测试表是按 id 拆分的,因而 id = 0 和 id = 1 的记录会落在不同的物理分片上(假如别离为分片 0 和分片 1)。例 1 读了分片 0,写了分片 1,而后提交了事务,这将会触发咱们对单分片写的“一阶段提交优化”。例 2 读了分片 0,随后写了分片 1 和 分片 0,而后提交了事务,这将会进行残缺的分布式事务提交流程。这两个例子还会触发“只读连贯优化”,即只有在第一次写的时候才真正开启分布式事务。在接下来的探讨中,咱们默认应用 TSO 事务策略和 RR 的隔离级别。例 1 事务的毕生 BEGIN 与 MySQL 相似,要开启一个事务,个别有两种形式。第一种形式是显式地执行 BEGIN 或 START TRANSACTION [transaction_characteristic],执行这两种语句,会调用 ServerConnection 中的 begin(boolean, IsolationLevel) 办法。第二种形式是执行 SET autocommit = 0,以后 session 会隐式开启事务,这种形式会调用 ServerConnection 中的 setAutocommit(boolean, boolean) 办法。两种形式都会调用 TConnection 的 setAutoCommit 办法。这些办法都只是简略地记录了一些变量(比方 transaction_characteristic 中设定的事务相干变量),同时标记这个连贯开启了事务。此时,事务对象也还没创立进去,也没有与后端连贯进行任何交互。读分片 0 在开启事务后,执行 SELECT * FROM tb1 WHERE id = 0 时,才会真正创立事务对象。依据事务与连贯中的探讨,在 TConnection 中这条逻辑 SQL 的执行入口为 executeSQL,外面会真正创立事务对象,次要执行逻辑为(代码出自 PolarDB-X 5.4.12 release 版本,为了不便阐明,有删减及改变,下同):// TConnection#executeSQL(ByteString, Parameters, TStatement, ExecutionContext)
public ResultSet executeSQL(ByteString sql, Parameters params, TStatement stmt,
ExecutionContext executionContext) throws SQLException {
if (this.trx == null || this.trx.isClosed()) {
// 开启事务后,直到执行第一条语句,才会创立事务对象。beginTransaction();
}
// 让 executionContext 援用 trx 对象,不便后续执行器通过 trx 对象拿物理连贯。
executionContext.setTransaction(this.trx);
resultCursor = executeQuery(sql, executionContext, trxPolicyModified);
}
// TConnection#beginTransaction(boolean)
private void beginTransaction(boolean autoCommit) {
// 依据一些默认的或用户设定的事务变量,抉择适合的事务策略,比方 TSO/XA 等。
trxPolicy = loadTrxPolicy(executionContext);
TransactionClass trxConfig = trxPolicy.getTransactionType(autoCommit, readOnly);
// 依据事务策略,创立出对应的事务对象。
this.trx = transactionManager.createTransaction(trxConfig, executionContext);
} 在咱们的例子中,如果在上述代码打个断点,能够看到创立进去的是 TsoTransaction,其中一些值得关注的变量为:trx = {TsoTransaction}
// 此时还没有获取任何工夫戳。snapshotTimestamp = -1
commitTimestamp = -1
// 事务写的第一个物理分片(下称主分片),事务日志将会写在这个分片上。primaryGroup = null
// 该事务是否跨分片,如果是单分片事务,会优化为一阶段提交。isCrossGroup = false
// 事务日志管理器,负责写事务日志。globalTxLogManager = {GlobalTxLogManager}
// 分布式事务的 connectionHolder 都是 TransactionConnectionHolder,// 外面别离存储了读写连贯,读连贯和写连贯在提交时行为会有所不同。connectionHolder = {TransactionConnectionHolder}
// 物理分片到对应写连贯的映射。groupHeldWriteConn = {HashMap}
// 物理分片到对应读连贯汇合到映射。在 ShareReadView 优化开启时,// 能够同时存在多个读连贯和一个写连贯,因而这里读写连贯须要离开治理。// 该优化不在本文开展。groupHeldReadConns = {HashMap} 在执行器阶段,会抉择给分片 0 下发一条 SELECT 语句,此时须要获取分片 0 的物理连贯,代码入口是 MyJdbcHandler 中的 getPhyConnection(ITransaction, ITransaction.RW, String, DataSource) 办法。其中的事务对象 Transaction 则是从 ExecutionContext 里拿到。该办法最初会调用 AbstractTransaction(这是所有分布式事务类的基类)中的 getConnection 办法。通过事务拿物理连贯的代码 AbstractTransaction#getConnection 如下:// AbstractTransaction#getConnection(String, String, IDataSource, RW, ExecutionContext)
public IConnection getConnection(String schema, String group, IDataSource ds, RW rw, ExecutionContext ec) {
if (/ 是事务的第一个写申请 /) {
// 把这个分片作为主分片,事务日志将写在这个分片上。this.primaryGroup = group;
// 该分片还用于生成 XA 事务的 xid。this.primaryGroupUid = IServerConfigManager.getGroupUniqueId(schema, group);
}
// 通过 connectionHolder 拿到物理连贯。
IConnection conn = connectionHolder.getConnection(schema, group, ds, rw);
if (/ 是写申请 / && !isCrossGroup && !this.primaryGroup.equals(group)) {
// 事务波及了多个分片。this.isCrossGroup = true;
}
return conn;
} 在咱们的例子中,上述参数 group 是物理分片 0,rw 是 READ,阐明须要物理分片 0 上的读连贯,ds 则次要用于生成物理连贯。因为咱们只是读申请,因而分片 0 不会作为主分片,会间接返回 connectionHolder.getConnection(schema, group, ds, rw) 的后果。通过连贯管理器拿物理连贯的代码 TransactionConnectionHolder#getConnection 如下:// TransactionConnectionHolder#getConnection(String, String, IDataSource, RW)
public IConnection getConnection(String schema, String group, IDataSource ds, RW rw) {
// 尝试获取该分片上的写连贯,如果有,间接返回这个写连贯。
HeldConnection groupWriteConn = groupHeldWriteConn.get(group);
if (groupWriteConn != null) {
return groupWriteConn.connection;
}
HeldConnection freeReadConn = / 尝试找到读连贯 /;
if (freeReadConn != null) {
if (/* 以后须要写连贯 */) {
// 设置以后连贯为写连贯,participated = true 意味着该连贯是写连贯。freeReadConn.participated = true;
this.groupHeldWriteConn.put(group, freeReadConn);
// 因为本来是读连贯,这里才真正开启分布式事务。this.trx.commitNonParticipant(group, freeReadConn.connection);
this.trx.begin(schema, freeReadConn.group, freeReadConn.connection);
}
return freeReadConn.connection;
}
// 以后分片没有任何连贯,创立一个新的连贯。还会依据读写类型,
// 设置好写连贯 groupHeldWriteConn 或读连贯汇合 groupHeldReadConns。
IConnection conn = new DeferredConnection(/ 这里会获取并封装该分片的公有协定连贯 /);
if (/ 须要写连贯 /) {
// 开启失常的分布式事务。this.trx.begin(schema, group, conn);
} else {
// 优化为只读事务。this.trx.beginNonParticipant(group, conn);
}
return conn;
} 在咱们的例子中,因为是第一条语句,该分片上还没有任何连贯,因而会学生成一个连贯该分片的公有协定连贯,包装成 DeferredConnection,而后因为是读申请,会调用 beginNonParticipant。TsoTransaction 的 beginNonParticipant 办法如下:// TsoTransaction#beginNonParticipant(String, IConnection)
protected void beginNonParticipant(String group, IConnection conn) throws SQLException {
if (snapshotTimestamp < 0) {
// 该事务从未拿过工夫戳,则在这里获取。snapshotTimestamp = nextTimestamp();
}
// 应用公有协定的流水线执行机制执行 BEGIN。
conn.executeLater(“BEGIN”);
// 在 BEGIN 后发送工夫戳。
sendSnapshotSeq(conn);
} 在咱们的例子中,公有协定连贯会流水线执行 BEGIN 语句(非阻塞,不等后果返回),且在稍后执行物理 SQL 时才发送工夫戳。至此,连贯上的一些初始化操作曾经实现,能够向执行器返回并执行读分片 0 的物理 SQL。写分片 1 随后,咱们执行 UPDATE tb1 SET a = 100 WHERE id = 1。在执行器阶段,须要给分片 1 下发一条 UPDATE 语句,此时须要获取分片 1 的物理连贯,因而又会调用 AbstractTransaction#getConnection 办法,通过事务对象拿物理连贯。通过后面贴出的代码,咱们发现因为是事务的第一个写申请,因而分片 1 会视作主分片,用于生成 xid 和稍后记录事务日志。在获取物理连贯时,又会调用 TransactionConnectionHolder#getConnection 办法,通过连贯管理器拿物理连贯。通过后面贴出的代码,咱们发现因为分片 1 没有任何连贯,因而会生成一个公有协定连贯,包装成 DeferredConnection。与读分片 0 不同,因为是写申请,会执行 TsoTransaction 的 begin 办法。TsoTransaction 的 begin 办法如下:// TsoTransaction#begin(String, String, IConnection)
protected void begin(String schema, String group, IConnection conn) throws SQLException {
if (snapshotTimestamp < 0) {
// 该事务从未拿过工夫戳,则在这里获取。snapshotTimestamp = nextTimestamp();
}
// 获取 xid。
String xid = getXid(group);
// 触发公有协定流水线执行 XA START。
conn.executeLater(“XA START ” + xid);
// 在 XA START 后发送工夫戳。
sendSnapshotSeq(conn);
} 简略来说,和此前 beginNonParticipant 办法惟一的区别在于,应用了 XA START 开启事务。依据 MySQL 对于 XA 事务的阐明,xid 由 gtrid [, bqual [, formatID]] 组成,这里的 gtrid 是“drds- 事务 id @主分片 Uid”,这样保障了同一个事务在不同分片上执行 XA START,会应用雷同的 gtrid。bqual 设置以后连贯的分片,用于在事务复原时确定分支事务所在的分片。在咱们的例子中,xid 为:’drds-13e101d74e400000@5ae6c3b5be613cd1′, ‘DB1_000001_GROUP’,其中 13e101d74e400000 是事务 id,5ae6c3b5be613cd1 是分片 1 的 Uid,DB1_000001_GROUP 是分片 1 的具体分片名称。值得注意的是,这里会把之前的工夫戳发送到分片 1。至此,连贯上的一些初始化操作曾经实现,能够向执行器返回并执行写分片 1 的物理 SQL。COMMIT 最初,执行 COMMIT 提交事务。解决 COMMIT 的代码入口为 ServerConnection#commit(),其次要调用了 TConnection#commit 办法,代码如下:// TConnection#commit()
public void commit() throws SQLException {
try {
// 触发事务的提交流程。this.trx.commit();
} finally {
// 大部分状况下,如果事务提交胜利,或出现异常后正确处理了,// 所有连贯会被敞开并开释,trx.close() 相当于什么也不做。// 但如果事务提交失败且没有解决,这里会回滚事务并开释所有物理连贯。this.trx.close();
// 去掉对这个事务对象的援用,意味着以后连贯里这个事务毕生的完结。this.trx = null;
}
} 咱们重点关注一下事务的提交流程,上述 this.trx.commit() 调用时,会调用 ShareReadViewTransaction(该类继承了 AbstractTransaction,基于 XA 事务实现了共享 readview 的性能,XATransaction 和 TsoTransaction 都会继承这个类,在这里能够简略了解为 XA 事务的基类)的 commit 办法,代码如下:// ShareReadViewTransaction#commit()
public void commit() {
if (!isCrossGroup) {
// 如果只波及了单个分片的写,进行一阶段提交优化。commitOneShardTrx();
} else {
// 失常的多分片分布式事务提交流程。commitMultiShardTrx();
}
} 在咱们的例子中,因为只写了分片 1,所以进入一阶段提交优化,代码如下:// ShareReadViewTransaction#commitOneShardTrx()
protected void commitOneShardTrx() {
// 对持有的所有物理连贯执行以下流程,以提交每个物理连贯开启的事务。
forEachHeldConnection((group, conn, participated) -> {
if (!participated) {
// 只读连贯是 BEGIN 开启事务的,且只有读操作,执行 ROLLBACK 即可。conn.execute("ROLLBACK");
} else {
// 获取 xid。String xid = getXid(group);
// 写连贯是 XA START 开启事务的,执行 XA END 和 XA COMMIT ONE PHASE 提交事务。conn.execute("XA END" + xid + "; XA COMMIT" + xid + "ONE PHASE");
}
});
// 所有连贯都提交了分支事务,开释并清空这些物理连贯。
connectionHolder.closeAllConnections();
} 咱们一共持有了 2 个物理连贯。对于分片 0 的只读连贯,会间接执行 ROLLBACK;对于分片 1 的写连贯,则执行 XA END 和 XA COMMIT ONE PHASE 提交事务。留神到咱们并没有获取 commit timestamp,因为在一阶段提交优化里,commit timestamp 会由 InnoDB 计算生成:具体的计算规定是:COMMIT_TS = MAX_SEQUENCE + 1,其中 MAX_SEQUENCE 为 InnoDB 本地保护的历史最大的 snapshot_ts。如果提交失败了,会调用事务的 close 办法,代码如下:// AbstractTransaction#close()
public void close() {
// 回滚所有物理连贯上的事务。
cleanupAllConnections();
// 开释并清空这些物理连贯。
connectionHolder.closeAllConnections();
} 值得一提的是,cleanupAllConnections() 也是 ROLLBACK 语句次要调用的办法。因而为了同时理解 ROLLBACK 语句执行流程,咱们也看一下 cleanupAllConnections 办法的代码:// AbstractTransaction#cleanupAllConnections()
protected final void cleanupAllConnections() {
// 对持有的所有物理连贯执行以下流程,以回滚每个物理连贯开启的事务。
forEachHeldConnection((group, conn, participated) -> {
if (conn.isClosed()) {return;}
if (!participated) {
// 只读连贯是 BEGIN 开启事务的,且只有读操作,执行 ROLLBACK 即可。conn.execute("ROLLBACK");
} else {
// 获取 xid。String xid = getXid(group);
// 写连贯是 XA START 开启事务的,执行 XA END 和 XA ROLLBACK 回滚事务。conn.execute("XA END" + xid + "; XA ROLLBACK" + xid);
}
});
} 能够看到,回滚的逻辑是看状况执行 ROLLBACK 或 XA ROLLBACK 来回滚事务的。至此,咱们看到了例 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 的分支事务。事务复原次要由 XARecoverTask 负责。其主体代码为 recoverInstance 办法,该办法查看一个 DN 下的所有 prepare 过的事务,并依据事务状态提交或回滚这些事务,代码如下:// XARecoverTask#recoverInstance(IDataSource, Set<String>)
// dataSource 用户获取 DN 的连贯,groups 是以后逻辑库的所有物理分片
private void recoverInstance(IDataSource dataSource, Set<String> groups) {
// 执行 XA RECOVER 获取该 DN 上所有 prepare 的事务。
// 此处省略了从 dataSource 获取连贯和生成 statement 的代码。
ResultSet rs = stmt.executeQuery(“XA RECOVER”);
while (rs.next()) {
// 对每一条记录,生成对象 PreparedXATrans,// 次要包含 xid,事务 id,分支事务所在的分片,主分片等信息。PreparedXATrans trans = /* 从 rs 中获取一行数据生成 */;
if (/* trans 所在分片是以后逻辑库的一个物理分片 &&
trans 在上一次 recover 工作时也呈现过,即较长时间都未被提交或回滚 */) {
// 染指解决这个分支事务:回滚或提交。rollBackOrForward(trans, stmt);
}
}
} 该工作每 5 秒到每个 DN 上执行一次 XA RECOVER,失去所有 prepare 过的事务,如果看到一个事务在上一次工作中也呈现过,即至多过来 5 秒都没有提交或回滚,则会抉择对这个事务进行回滚或提交。相干逻辑代码为 rollBackOrForward,代码如下:// XARecoverTask#rollBackOrForward(PreparedXATrans, Statement)
private boolean rollBackOrForward(PreparedXATrans trans, Statement stmt) throws SQLException {
String primaryGroup = / 从 trans 里解析出主分片,详见前文对于 xid 的生成 /;
// 尝试从主分片的事务日志中找到相干的事务日志。
GlobalTxLog tx = GlobalTxLogManager.get(primaryGroup, trans.transId);
if (tx != null) {
// 的确存在事务日志,依据事务日志状态判断回滚或提交。if (tx.getState() == TransactionState.ABORTED) {
// ABORTED 状态的事务须要回滚。return tryRollback(stmt, trans);
} else {
// SUCCESS 状态的事务须要提交。return tryCommitTSO(stmt, trans, tx.getCommitTimestamp());
}
} else {
// 没有找到事务日志,尝试回滚,先开启事务,写下事务日志,标记事务为 ABORTED。try (Connection conn2 = /* 获取主分片上的另一个连贯 */;) {conn2.setAutocommit(false);
// 尝试回滚,如果因为别的线程正在解决这个事务
//(比方该事务只是提交得慢,连贯仍未断开,还在提交流程)// 而报错,就回滚上一条写事务日志的语句。txLog.append(transInfo.transId, TransactionType.XA, TransactionState.ABORTED, new ConnectionContext(), conn2);
stmt.execute("XA ROLLBACK" + trans.toXid());
conn2.commit();
return true;
} catch (Exception e) {/* 依据异样判断是否要回滚写事务日志的操作 */}
}
} 该办法次要有 3 段逻辑。一是如果存在对应的事务日志,且事务状态是 ABORTED,那就执行 tryRollback 办法回滚,该办法次要执行了 XA ROLLBACK {xid}。二是如果事务状态是 SUCCESS,那就执行 tryCommitTSO 办法回滚,该办法会设置 commit 工夫戳,而后执行 XA COMMIT {xid}。三是如果没找到事务日志,此时个别有两种可能:1)事务提交失败了,且没写下事务日志,此时须要回滚;2)事务还在两阶段提交的流程中,只是 prepare 较慢,还没开始写事务日志,此时不须要做任何操作。在 MySQL 中,如果发动 XA START 的连贯没有敞开,其余连贯是无奈通过 XA ROLLBACK 或 XA COMMIT 来回滚或提交这个分支事务的。咱们利用这一个性,首先在事务日志插入一条 ABORTED 记录,表明这个分布式事务须要回滚,而后尝试执行 XA ROLLBACK 回滚这个分支事务。如果遇到 2)的状况,则会收到特定的报错,此时再回滚掉插入 ABORTED 事务日志的操作。在咱们插入 ABORTED 事务日志后,本来正在提交事务的线程在插入 SUCCESS 事务日志时会被阻塞,而在咱们回滚掉插入 ABORTED 事务日志的操作后,事务提交流程就会持续进行上来。小结本文次要解读了 PolarDB-X 中 CN 端的事务相干的代码,以 TSO 事务为主,应用两个例子,一步步地展现了事务的开启、执行、提交、复原等流程。心愿大家浏览本文后,能更加理解 PolarDB-X 的事务零碎。原文链接:http://click.aliyun.com/m/100… 本文为阿里云原创内容,未经容许不得转载。