Skip to content

分布式数据库原理(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)确认才返回成功。

go
// 伪代码: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)

事务流程:

  1. PreWrite 阶段:从 PD 获取一个全局单调递增的 TSO 作为事务 start_ts。事务涉及的所有 Key 写入 TiKV 的锁(Lock),同时将数据写入默认 CF(Column Family)。
  2. Commit 阶段:选择一个 Key 作为 Primary Lock,先提交 Primary(写入 Commit 标记),再并行提交 Secondary。如果 Primary 提交成功,整个事务就提交了。
  3. 清理阶段:清除锁信息。
java
// 伪代码: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 事务模型

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。