一、分布式事务的烦恼
想象一下你去银行转账的场景:你要从账户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就像婚礼现场的"我愿意"环节,分为两个阶段:
- 准备阶段:协调者询问所有参与者"能提交吗?"
- 提交阶段:如果全部回答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基础上做了多项优化:
- 全局时间戳:通过TSO服务分配全局单调递增的时间戳,解决跨节点事务的可见性问题
- 合并提交:对多个分片的小事务批量处理,减少网络往返
- 异步提交:非关键路径事务允许延迟提交,提升吞吐量
一个典型的生产环境示例:
// 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 | 强一致 | 较低 | 高一致性要求的资金操作 |
实际应用中的坑:
- 跨机房部署时,2PC的提交延迟可能高达数百毫秒
- 协调者单点问题需要通过ETCD等实现高可用
- 长时间未决事务会占用连接资源,需要设置超时
五、最佳实践与总结
- 熔断设计:当跨分区事务失败率超过阈值时,自动降级为本地事务
- 监控指标:重点关注
xa_prepare_time和xa_commit_retry_count - 事务拆分:大事务拆分为多个小事务,避免全局锁竞争
正如我们在PolarDB中看到的,现代分布式数据库通过2PC与优化技术的结合,在保证一致性的同时,也兼顾了性能需求。不过记住,没有银弹——对于秒杀等高并发场景,可能需要牺牲强一致性换取吞吐量。
评论