摘要
分布式事务是分布式数据库面临的核心技术挑战之一。传统两阶段提交(2PC)协议虽然被广泛使用,但在性能和可用性方面存在固有局限。本文对比分析传统 2PC 与 TiDB 基于 Percolator 模型的分布式事务实现,说明 TiDB 如何在保证 ACID 的同时实现高性能的事务处理。
本文适合谁: 正在研究分布式事务实现的架构师、后端工程师。
一、为什么需要分布式事务?
当数据分布在多个物理节点上时,一次业务操作可能涉及跨节点的数据修改。例如:
- 电商下单:扣减库存(节点 A)+ 创建订单(节点 B)+ 扣减余额(节点 C)
- 银行转账:转出账户(节点 A)+ 转入账户(节点 B)
这些操作必须作为一个原子单元执行——要么全部成功,要么全部回滚,否则会产生数据不一致。
二、传统两阶段提交(2PC)
2.1 2PC 的工作流程
阶段一(Prepare):协调者向所有参与者发送 prepare 请求
→ 参与者执行事务并锁定资源,回复 yes/no
阶段二(Commit/Rollback):
→ 所有参与者回复 yes → 协调者发送 commit
→ 任一参与者回复 no → 协调者发送 rollback
2.2 2PC 的问题
| 问题 | 说明 |
|---|---|
| 阻塞问题 | 参与者在等待协调者响应期间持有锁,长时间阻塞 |
| 单点故障 | 协调者宕机导致参与者长时间锁定资源 |
| 性能开销 | 两轮网络往返(2 RTT),增加延迟 |
| 扩展性差 | 参与者越多,协调成本越高,吞吐越低 |
三、TiDB Percolator 模型原理
TiDB 的分布式事务基于 Google Percolator 论文的设计思想,与传统 2PC 有本质区别。
3.1 核心机制:时间戳(Timestamp)
TiDB 使用 PD(Placement Driver)分配全局单调递增时间戳,作为事务的版本标识。
事务开始 → PD 分配 start_ts(读时间戳)
事务写入 → PD 分配 commit_ts(提交时间戳)
start_ts < commit_ts,确保事务间的线性一致性
3.2 乐观事务流程
1. Prewrite 阶段:
- 客户端将数据写入缓存,生成写意图(Write Intent)
- 向 TiKV 各 Region 发送 Prewrite 请求
2. Commit 阶段:
- PD 分配 commit_ts
- 将写意图标记为已提交(Committed)
- 写入 Commit 记录
3. 读取阶段:
- 遇到未提交的写意图,根据 commit_ts 判断可见性
- 后台清理过期写意图
3.3 与 2PC 的关键区别
| 维度 | 传统 2PC | TiDB Percolator |
|---|---|---|
| 协调者 | 专用协调者节点(单点) | PD(多副本 Raft,无单点) |
| 锁机制 | 悲观锁,Prepare 阶段加锁 | 乐观锁,Prewrite 阶段检测冲突 |
| 阻塞行为 | 长时间阻塞 | 写冲突时立即失败重试 |
| 单点故障 | 协调者宕机需恢复 | PD 多副本保证高可用 |
| 网络开销 | 2 RTT(固定) | 2 RTT(正常)/ 1 RTT(1PC 优化) |
3.4 1PC 优化
对于只涉及单个 Region 的事务,TiDB 可将 Prewrite 和 Commit 合并为一次请求,只需 1 RTT,大幅降低延迟。
单 Region 事务:Prewrite + Commit → 1 RTT
多 Region 事务:Prewrite + Commit → 2 RTT
异步 Commit:Prewrite 后立即返回,Commit 异步完成 → 最小化客户端等待
四、ACID 在分布式环境下的保证
TiDB 作为分布式数据库,完整支持 ACID 四大特性:
| 特性 | TiDB 保证方式 |
|---|---|
| A(原子性) | Percolator 两阶段写入,要么全部提交,要么全部回滚 |
| C(一致性) | 行级约束、外键检查、唯一索引等在分布式环境下完整支持 |
| I(隔离性) | 默认快照隔离(SI),支持 RC(Read Committed) |
| D(持久性) | Raft 多数副本写入持久化存储后才返回成功 |
五、TiDB 事务使用示例
5.1 乐观事务(默认)
BEGIN OPTIMISTIC;
SELECT balance FROM accounts WHERE id = 1; -- balance = 1000
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 如果 commit_ts 范围内无冲突写入,提交成功
-- 如果存在写冲突,返回 ERROR 9007 (Try again later)
5.2 悲观事务
BEGIN PESSIMISTIC;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 加锁
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 读取时即加锁,避免冲突重试,适用于高冲突场景
5.3 事务模式选择建议
| 场景 | 推荐模式 |
|---|---|
| 低冲突写入(日志、时序) | 乐观事务 |
| 高冲突写入(余额、库存) | 悲观事务 |
| 批量导入 | 乐观事务 + 小批次 |
| 跨 Region 大事务 | 评估事务大小,必要时拆分 |
六、FAQ
Q1:TiDB 分布式事务延迟多少?
- 单 Region 事务(1PC):网络延迟 ≈ 1 RTT + 2 次 RPC
- 多 Region 事务(2PC):网络延迟 ≈ 2 RTT + 3 次 RPC
- 在同城部署(RTT < 1ms)下,单笔事务端到端延迟通常在 2-5ms
Q2:什么时候用悲观事务?
当业务存在大量写冲突时(如扣减余额、库存预占),推荐使用悲观事务。悲观事务在读取时即加锁,避免乐观事务的冲突重试开销,在高并发写场景下性能更稳定。
Q3:TiDB 支持多大事务?
默认单事务大小限制为 100MB(由 `tidb_config` 中的 `txn-total-size-limit` 控制)。建议单事务修改的 Key 数量不超过 10,000 个,超过此阈值可能导致提交延迟显著增加。
Q4:分布式事务有性能损失吗?
相比单机事务,分布式事务有以下额外开销:
- 网络延迟: 多 Region 事务需额外 1-2 次 RTT
- 写入放大: 每个写入需要生成时间戳和版本记录
TiDB 通过 1PC 优化(单 Region 事务)、异步 Commit、批量写入等技术将额外开销控制在合理范围。在同城部署下,分布式事务的性能损耗通常在 10-30% 以内。
七、总结
分布式事务是分布式数据库的核心能力。传统 2PC 虽然概念简单,但在阻塞、单点故障、扩展性等方面存在固有缺陷。TiDB 基于 Percolator 模型,通过全局时间戳、乐观/悲观事务、1PC 优化等机制,在保证严格 ACID 的同时实现了高性能的分布式事务处理。工程师可根据业务冲突特征灵活选择事务模式,在延迟和吞吐之间取得最优平衡。
下一步行动
- 试用 TiDB 分布式事务: 下载 TiDB 并体验事务能力
- 深入了解事务原理: 阅读 TiDB 事务模型文档
- 获取迁移方案: 联系 TiDB 技术顾问评估迁移路径