一、分布式事务的烦恼

想象一下你去银行转账的场景:你要从账户A转100元到账户B。在传统单机数据库里,这个操作很简单——先扣减A的余额,再增加B的余额,两个操作在一个事务里要么都成功,要么都失败。但如果是跨分区的分布式数据库呢?A账户可能在上海节点,B账户可能在北京节点,这时候怎么保证"要么全做,要么全不做"?这就是分布式事务要解决的问题。

举个具体例子,假设我们用PolarDB的分布式版,执行如下转账操作:

-- PolarDB-X示例(基于MySQL语法)
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 'A';  -- 上海分片
UPDATE account SET balance = balance + 100 WHERE user_id = 'B';  -- 北京分片
COMMIT;

如果第二个UPDATE因为网络问题失败了,第一个UPDATE却已经生效,就会出现A的钱没了但B没收到的情况。这就是典型的分布式事务一致性问题。

二、两阶段提交(2PC)的原理

2PC就像婚礼现场的"我愿意"环节,分为两个阶段:

  1. 准备阶段:协调者询问所有参与者"能提交吗?"
  2. 提交阶段:如果全部回答YES,就发提交命令;只要有一个NO,就全体回滚

用PolarDB的XA事务实现来看:

-- 第一阶段:准备
XA START 'transaction_id';
UPDATE account SET balance = balance - 100 WHERE user_id = 'A';  -- 执行但不提交
XA END 'transaction_id';
XA PREPARE 'transaction_id';  -- 将事务状态持久化

-- 其他节点同样执行PREPARE...

-- 第二阶段:决策
XA COMMIT 'transaction_id';  -- 所有PREPARE成功后才提交

这个过程的精妙之处在于:

  • 准备阶段会先把修改写入磁盘(WAL日志)
  • 协调者崩溃恢复后可以查询参与者状态继续处理
  • 参与者超时未收到指令会自动回滚

三、PolarDB的分布式事务实现

阿里云PolarDB在2PC基础上做了多项优化:

  1. 全局时间戳:通过TSO服务分配全局单调递增的时间戳,解决跨节点事务的可见性问题
  2. 合并提交:对多个分片的小事务批量处理,减少网络往返
  3. 异步提交:非关键路径事务允许延迟提交,提升吞吐量

一个典型的生产环境示例:

// Java应用使用PolarDB分布式事务(JDBC示例)
try (Connection conn = dataSource.getConnection()) {
    conn.setAutoCommit(false);
    
    // 分片1操作
    PreparedStatement stmt1 = conn.prepareStatement(
        "UPDATE orders SET status = 'paid' WHERE order_id = ?");
    stmt1.setString(1, "order123");
    stmt1.executeUpdate();
    
    // 分片2操作  
    PreparedStatement stmt2 = conn.prepareStatement(
        "INSERT INTO payment_log VALUES(?, ?, NOW())");
    stmt2.setString(1, "order123");
    stmt2.setBigDecimal(2, new BigDecimal("100.00"));
    stmt2.executeUpdate();
    
    conn.commit();  // PolarDB会自动转换为XA事务
} catch (SQLException e) {
    // 自动触发跨分片回滚
}

四、技术对比与选型建议

方案 一致性 性能 适用场景
2PC 强一致 中等 金融交易、订单支付
Saga 最终 长流程业务(如物流)
TCC 强一致 较低 高一致性要求的资金操作

实际应用中的坑

  1. 跨机房部署时,2PC的提交延迟可能高达数百毫秒
  2. 协调者单点问题需要通过ETCD等实现高可用
  3. 长时间未决事务会占用连接资源,需要设置超时

五、最佳实践与总结

  1. 熔断设计:当跨分区事务失败率超过阈值时,自动降级为本地事务
  2. 监控指标:重点关注xa_prepare_timexa_commit_retry_count
  3. 事务拆分:大事务拆分为多个小事务,避免全局锁竞争

正如我们在PolarDB中看到的,现代分布式数据库通过2PC与优化技术的结合,在保证一致性的同时,也兼顾了性能需求。不过记住,没有银弹——对于秒杀等高并发场景,可能需要牺牲强一致性换取吞吐量。