分布式数据库原理(TiDB)
提出问题
"单机数据库不够用了,怎么办?"这是在数据量突破 TB 级、QPS 要求上万时,每个 DBA 和架构师都会面对的问题。传统分库分表(Sharding)方案引入了复杂的路由逻辑、跨节点查询和扩容难题,运维成本直线上升。TiDB 作为 NewSQL 的代表,承诺让使用者像操作单机数据库一样使用分布式数据库——自动分片、弹性伸缩、强一致、高可用。面试官问 TiDB 原理,考察的不只是你会不会用,而是你理解分布式数据库在存储、事务、调度这三个核心维度上如何做权衡。
分析问题
计算与存储分离架构
TiDB 采用三层解耦架构:TiDB Server 作为无状态计算层,负责 SQL 解析、优化和执行;TiKV 作为分布式 Key-Value 存储层,负责数据持久化;PD (Placement Driver) 是全局调度中心,负责元数据管理和负载均衡。
┌───────────────────────────────────────────────┐
│ TiDB Server (计算层,无状态) │
│ SQL Parsing → Optimizer → Executor → Coprocessor│
└──────────────────────┬────────────────────────┘
│ gRPC
┌──────────────────────▼────────────────────────┐
│ PD (调度层) │ TSO 时间戳 │ Region 调度 │
└──────┬─────────────────────────────────────────┘
│ gRPC
┌──────▼─────────────────────────────────────────┐
│ TiKV (存储层) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Region 1 │ │ Region 2 │ │ Region 3 │ ... │
│ │ Raft │ │ Raft │ │ Raft │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 每个 Region = 3 副本 (Raft Group) │
└────────────────────────────────────────────────┘这种分离的好处是计算层可以水平扩展,存储层也可以独立扩缩,不像传统读写分离那样受限于单机瓶颈。
Raft 共识与 Region 自动分裂
TiKV 按 Key 范围将数据切分成若干 Region(默认 96MB),每个 Region 是一个独立的 Raft Group。Raft 保证了副本之间的强一致性——写操作必须得到多数派(quorum)确认才返回成功。
// 伪代码:Region 分裂逻辑
func (s *Store) maybeSplitRegion(region *Region) {
if region.Size() > MaxRegionSize { // 默认 96MB
splitKey := findSplitKey(region) // 按数据量均匀切分
left, right := region.Split(splitKey)
pd.ReportSplit(left, right)
// 新 Region 也会自动复制到其他 Store
}
}PD 会持续监控每个 Region 的大小和访问热度。当 Region 超过阈值时自动分裂,当某个 Store 的 Region 数过多时,PD 会自动调度迁移。这套机制让 TiDB 做到了自动分片——运维人员不需要像 MySQL 分库分表那样预先规划分区键和分片数量。
Percolator 分布式事务模型
TiDB 的事务模型源自 Google 的 Percolator 论文,基于 乐观锁 + 两阶段提交 + 全局时间戳(TSO)。
事务流程:
- PreWrite 阶段:从 PD 获取一个全局单调递增的 TSO 作为事务 start_ts。事务涉及的所有 Key 写入 TiKV 的锁(Lock),同时将数据写入默认 CF(Column Family)。
- Commit 阶段:选择一个 Key 作为 Primary Lock,先提交 Primary(写入 Commit 标记),再并行提交 Secondary。如果 Primary 提交成功,整个事务就提交了。
- 清理阶段:清除锁信息。
// 伪代码:Percolator 事务提交
public boolean commit(Transaction txn) {
long commitTs = pd.getTimestamp(); // 全局 TSO
// 1. PreWrite: 所有 key 写锁 + 数据
for (Key key : txn.writes) {
tikv.prewrite(key, txn.getData(key), txn.startTs, txn.primaryLock);
}
// 2. Commit Primary: 决定事务成败
boolean success = tikv.commit(txn.primaryLock, txn.startTs, commitTs);
if (!success) return false; // 冲突,回滚
// 3. Commit Secondary: 异步并行提交
for (Key key : txn.writes) {
if (!key.equals(txn.primaryLock)) {
tikv.commitAsync(key, txn.startTs, commitTs);
}
}
return true;
}这种乐观事务模型在读多写少场景下性能很好,但写冲突频繁时重试开销大。TiDB 6.0+ 引入了悲观锁(SELECT ... FOR UPDATE),在冲突率高的场景下避免乐观重试的性能抖动。
HTAP 混合负载
TiDB 的另一个卖点是 HTAP(Hybrid Transactional/Analytical Processing)。通过引入 TiFlash 列存副本,TiDB 能在同一份数据上同时支持 OLTP 和 OLAP 查询。
TiKV (行存) ←→ Raft Learner → TiFlash (列存)
↑ ↑
│ │
OLTP 写入/点查 OLAP 分析查询TiFlash 作为 TiKV 的 Raft Learner 节点,通过 Raft 日志实时同步数据,保证秒级一致性。分析查询自动路由到 TiFlash,不影响 TP 链路的延迟。
总结
TiDB 的核心设计哲学是"让分布式像单机一样简单",通过三层架构解耦、Raft 强一致、自动 Region 分裂实现弹性伸缩,Percolator 模型提供分布式事务,TiFlash 列存实现 HTAP。
面试话术示例:
"TiDB 用 Raft 做副本一致性,用 Region 分裂做自动分片,用 Percolator 两阶段提交做分布式事务。计算与存储分离让它能独立扩缩,PD 做全局调度。适合数据量超 TB 需要弹性扩张、且不想绑定分库分表中间件的场景。但乐观事务在冲突率高时性能下降明显,需要评估业务写冲突模式。"
生产避坑:Region 过多(>10 万)会导致 PD 调度压力增大;乐观事务在高冲突场景下建议启用悲观锁;TiFlash 副本建议在分析查询稳定后再增加,避免资源浪费。
参考
参考:Percolator: Large-Scale Incremental Processing Using Distributed Transactions and Notifications;TiDB 官方文档 TiKV 架构 和 Percolator 事务模型