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


读分片 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) 的结果 。

相关经验推荐