0
0
1
0
博客/.../

TiDB 架构深度解析

 北南南北  发表于  2026-05-26

一、从关系表到 KV 编码

理解 TiDB 架构的第一步,是搞清楚关系型数据在底层是如何存储的。

1.1 行数据的编码

在 TiDB 中,每一行数据最终都会被编码为 KV 对存储在 TiKV 中。来看一个具体例子:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(64),
    age INT
);

INSERT INTO users VALUES (100, 'Alice', 28);

这行数据在 TiKV 中的存储形式:

Key 编码规则:  t{table_id}_r{row_id}
Value 编码:    列值按照列顺序编码

假设 table_id = 56:

Key:   t56_r100
Value: ["Alice", 28]  (经过编码的二进制数据)

查看表的内部 ID:

SELECT TIDB_TABLE_ID FROM information_schema.tables
    WHERE table_schema = DATABASE() AND table_name = 'users';

-- 假设查询结果为 56
-- 则该表数据在 TiKV 中的 key 前缀为: t56_r

1.2 索引的编码

二级索引同样是 KV 对,编码规则略有不同:

CREATE INDEX idx_name ON users(name);

索引在 TiKV 中的存储:

Key:   t{table_id}_i{index_id}_{indexed_value}
Value: {row_id}  (回表用的主键)

假设 index_id = 1:

Key:   t56_i1_"Alice"
Value: 100

当执行 SELECT * FROM users WHERE name = 'Alice' 时:

步骤1: 通过索引 Key (t56_i1_"Alice") 查到 Value = 100
步骤2: 用行数据 Key (t56_r100) 查到完整行数据

这就是所谓的回表操作。如果查询只需要索引列,就能直接从索引获取结果,不需要回表,这叫做覆盖索引

1.3 联合索引的编码

CREATE INDEX idx_status_age ON users(status, age);
Key:   t{table_id}_i{index_id}_{status}_{age}_{row_id}
Value: (空,因为 row_id 已经在 Key 中了)

联合索引遵循最左前缀原则:
- WHERE status = 1           → 可以用索引
- WHERE status = 1 AND age = 28  → 可以用索引
- WHERE age = 28             → 不能用索引(缺少最左列 status)

1.4 实际验证

通过以下 SQL 可以看到 TiDB 是如何编码数据的:

-- 查看表的内部信息
SELECT
    TABLE_NAME,
    TIDB_TABLE_ID,  -- 表 ID,用于构造 Key
    TIDB_ROW_ID_SHARDING_INFO
FROM information_schema.tables
WHERE table_name = 'users';

-- 查看索引信息
SHOW INDEX FROM users;

二、TiDB Server:无状态的计算层

2.1 架构定位

TiDB Server 是无状态的 SQL 计算引擎。"无状态"意味着:

  • TiDB Server 不存储任何数据
  • 数据全部在 TiKV 中
  • 可以随时启停 TiDB Server,不影响数据
  • 新增 TiDB Server 无需数据迁移
        ┌──────────┐
        │ 应用连接  │
        └────┬─────┘
             │
    ┌────────┼────────┐
    v        v        v
┌──────┐┌──────┐┌──────┐
│TiDB-1││TiDB-2││TiDB-3│  三个计算节点,无状态
└──┬───┘└──┬───┘└──┬───┘
   │       │       │
   └───────┼───────┘
           │
    ┌──────┴──────┐
    │   TiKV      │  数据存储层
    └─────────────┘

2.2 SQL 执行流程

一条 SQL 在 TiDB Server 内部的执行过程:

SQL 语句
  │
  v
┌─────────────────────────────────────────┐
│              Parser (解析器)             │
│  SQL → AST (抽象语法树)                  │
│  SELECT * FROM t WHERE id = 1            │
│  → SelectStmt { Fields: *, Where: id=1 }│
└─────────────────┬───────────────────────┘
                  v
┌─────────────────────────────────────────┐
│          Preprocess (预处理器)            │
│  语义检查:表是否存在、列是否存在          │
│  权限检查:用户是否有权限                  │
└─────────────────┬───────────────────────┘
                  v
┌─────────────────────────────────────────┐
│           Optimizer (优化器)              │
│  生成执行计划:选择索引、确定 Join 方式    │
│  逻辑优化:谓词下推、列裁剪               │
│  物理优化:选择代价最小的物理算子          │
└─────────────────┬───────────────────────┘
                  v
┌─────────────────────────────────────────┐
│          Executor (执行器)               │
│  将执行计划转换为对 TiKV 的 KV 请求       │
│  聚合结果返回给客户端                     │
└─────────────────────────────────────────┘

2.3 执行计划的构成

TiDB 的执行计划由多个算子(Operator)组成,每个算子负责一个特定的操作:

EXPLAIN SELECT * FROM users WHERE age > 25 ORDER BY name LIMIT 10;
+-------------------------------+---------+-----------+----------------------+
| id                            | estRows | task      | operator info        |
+-------------------------------+---------+-----------+----------------------+
| TopN_9                        | 10.00   | root      | users.name, offset:0 |
| └─TableReader_16              | 10.00   | root      | data:TopN_15         |
|   └─TopN_15                   | 10.00   | cop[tikv] | users.name, offset:0 |
|     └─Selection_14            | 3333.33 | cop[tikv] | gt(users.age, 25)    |
|       └─TableFullScan_13      | 10000   | cop[tikv] | table:users          |
+-------------------------------+---------+-----------+----------------------+

各算子含义:

算子 含义
TableFullScan 全表扫描
Selection 过滤条件(WHERE age > 25)
TopN Top N 排序(ORDER BY ... LIMIT)
TableReader 从 TiKV 读取表数据的 Reader

task 列说明

task 执行位置
root 在 TiDB Server 执行
cop[tikv] 下推到 TiKV 执行(协处理器)

可以看到,TiDB 尽量将计算下推到 TiKV 执行,这样可以减少数据传输量。


三、PD:集群的大脑

3.1 PD 的核心职责

PD(Placement Driver)虽然不直接参与数据读写,但它是整个集群的核心控制组件:

┌─────────────────────────────────────────────────┐
│                    PD Server                      │
│                                                   │
│  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
│  │ 元数据管理 │  │ 调度中心  │  │ TSO 时间戳服务 │  │
│  └──────────┘  └──────────┘  └───────────────┘  │
│                                                   │
│  存储集群拓扑、Region 分布、节点状态                │
│  决定 Region 在哪里、是否需要迁移                   │
│  为分布式事务提供全局递增时间戳                     │
└───────────────────────────────────────────────────┘

3.2 元数据管理

PD 维护了完整的集群元数据:

  • Store 信息:每个 TiKV 节点的地址、容量、状态
  • Region 信息:每个 Region 的 Key 范围、所在 TiKV 节点、副本分布
  • 表到 Region 的映射:哪些表数据在哪些 Region 上

TiDB Server 启动时,会从 PD 拉取完整的元数据缓存在本地。之后 TiDB Server 可以直接根据元数据定位数据所在 Region,并与 TiKV 直接通信,不再经过 PD。

3.3 调度中心

PD 持续监控集群状态,并在以下情况下触发调度:

触发条件 调度动作
新增/下线 TiKV 节点 迁移 Region 到新/其他节点
Region 分布不均 将 Region 从高负载节点迁走
Region 过大 分裂 Region(Split)
热点 Region 分裂热点 Region,分散负载
副本数不足 为新 Region 创建副本

调度是后台自动进行的,不影响业务正常运行。

3.4 TSO(全局时间戳服务)

TSO 是 PD 提供的全局递增时间戳服务,是 TiDB 实现分布式事务的基础:

TiDB-1 ─── 获取 TSO ──> PD
                         │
                         v
                     返回: 428888888888888321
                         (全局唯一递增时间戳)

TiDB-2 ─── 获取 TSO ──> PD
                         │
                         v
                     返回: 428888888888888322
                         (比上一个更大)

这个全局时间戳确保了:

  • 分布式事务的顺序性
  • 快照隔离的一致性
  • 跨节点操作的可串行化

3.5 PD 的高可用

PD 基于 Raft 协议实现高可用,典型部署为 3 或 5 个节点:

PD-1 (Leader)  ← 处理所有读写请求
PD-2 (Follower) ← 同步数据,可接替 Leader
PD-3 (Follower) ← 同步数据,可接替 Leader

当 Leader 故障时,剩余节点通过 Raft 选举产生新 Leader。只要多数派节点存活,PD 就能正常工作。


四、TiKV:分布式事务存储引擎

4.1 Region — 数据分布的基本单位

TiKV 中的数据不是整块存放的,而是被切分为一个个 Region:

Key 空间
│
├─── Region A: [Key 0001, Key 5000]
│    ├─ Replica 1 → TiKV Node 1 (Leader)
│    ├─ Replica 2 → TiKV Node 2 (Follower)
│    └─ Replica 3 → TiKV Node 3 (Follower)
│
├─── Region B: [Key 5001, Key 10000]
│    ├─ Replica 1 → TiKV Node 2 (Leader)
│    ├─ Replica 2 → TiKV Node 3 (Follower)
│    └─ Replica 3 → TiKV Node 1 (Follower)
│
└─── Region C: [Key 10001, Key 15000]
     ...

每个 Region:

  • 默认大小 96 MB,超过后自动分裂
  • 有 3 个副本(默认),通过 Raft 保持一致
  • 每个副本有一个 Leader,负责处理读写;Follower 只同步数据

4.2 Raft 协议简述

Raft 是一种分布式一致性协议,确保多个副本的数据一致:

写请求到达 Region Leader
        │
        v
Leader 将写入写入本地 WAL
        │
        v
Leader 将数据发给 Follower
        │
        v
收到多数派 (2/3) 确认后提交
        │
        v
返回成功给客户端

关键点:只要多数派(3 副本中的 2 个)存活,数据就不会丢失,服务就不会中断。

4.3 TiKV 的存储引擎

TiKV 底层使用 RocksDB 作为存储引擎,其数据结构为 LSM-Tree:

写请求
  │
  v
MemTable (内存中的跳表)  ← 写入先到内存
  │
  v  当 MemTable 满时
SST File (磁盘上的有序文件)
  │
  v  多层合并
L0 → L1 → L2 → ... → Ln

LSM-Tree 的特点:

  • 写入性能高:追加写,不需要随机写
  • 读取需要合并:数据可能分布在多层,需要合并查找
  • 适合写多读少的场景

为了加速读取,TiKV 使用了 Block CacheRocksDB 的 Bloom Filter

读取 Key
  │
  v
Block Cache 中查找
  │ 未命中
  v
Bloom Filter 判断"一定不存在"还是"可能存在"
  │ 可能存在
  v
从 L0 → L1 → ... 逐层查找

4.4 MVCC(多版本并发控制)

TiKV 使用 MVCC 来支持并发读写的一致性:

同一行数据可能有多个版本:

Key: t56_r100
Version 1 (TSO=100): value = "Alice, age 28"
Version 2 (TSO=200): value = "Alice, age 29"
Version 3 (TSO=300): value = "Alice, age 30"

每个事务获取数据时,根据事务开始时的 TSO 确定能看到哪个版本:

事务 A (start_ts=150): 看到 Version 1 (Alice, age 28)
事务 B (start_ts=250): 看到 Version 2 (Alice, age 29)
事务 C (start_ts=350): 看到 Version 3 (Alice, age 30)

这样不同事务之间不会互相阻塞,提高了并发性能。

4.5 两阶段提交(2 PC)

TiDB 的分布式事务使用 Percolator 模型,其核心是两阶段提交:

事务:UPDATE users SET age = 30 WHERE id = 100

第一阶段:Prewrite
┌─────────────────────────────────────────┐
│ 1. 选择一行作为 Primary Lock            │
│    其他行为 Secondary Lock              │
│ 2. 将 Lock 写入所有涉及的 Key           │
│ 3. 将实际数据写入(带 Lock 标记)        │
│ 4. 先写 Primary,再写 Secondary         │
└────────────────┬────────────────────────┘
                 v
第二阶段:Commit
┌─────────────────────────────────────────┐
│ 1. 向 PD 获取 commit_ts                  │
│ 2. 提交 Primary Lock                     │
│ 3. 异步提交 Secondary Lock              │
│ 4. 返回成功给客户端                      │
└─────────────────────────────────────────┘

为什么先提交 Primary?

只要 Primary 提交成功,事务就成功了。Secondary 可以异步完成,即使 Secondary 提交失败,后续读取也可以通过 Resolve Lock 机制帮助完成提交。


五、组件间的协作

5.1 完整的读写流程

                     写入流程
  ┌───────────────────────────────────────────┐
  │                                            │
  │  客户端                                     │
  │    │                                       │
  │    v                                       │
  │  TiDB Server                                │
  │    │  1. SQL 解析 → 执行计划                │
  │    │  2. 获取 commit_ts (TSO)               │
  │    │  3. Prewrite: 写入 Lock                │
  │    │     → 直接发往 TiKV Region Leader      │
  │    │  4. Commit: 提交事务                    │
  │    │     → 先提交 Primary,再异步 Secondary  │
  │    v                                       │
  │  TiKV                                       │
  │    │  1. 接收 KV 请求                       │
  │    │  2. 写入 MemTable/WAL                  │
  │    │  3. Raft 复制到 Follower               │
  │    │  4. 返回确认                           │
  │    v                                       │
  │  PD (仅在获取 TSO 和元数据时参与)            │
  │                                            │
  └───────────────────────────────────────────┘


                     读取流程
  ┌───────────────────────────────────────────┐
  │                                            │
  │  客户端                                     │
  │    │                                       │
  │    v                                       │
  │  TiDB Server                                │
  │    │  1. SQL 解析 → 确定访问哪些 Region     │
  │    │  2. 构造 KV 请求                        │
  │    │  3. 直接发送给对应 TiKV Region Leader  │
  │    │  4. 聚合结果返回客户端                  │
  │    v                                       │
  │  TiKV                                       │
  │    │  1. 接收 KV 读取请求                    │
  │    │  2. 根据 MVCC 找到对应版本              │
  │    │  3. 返回数据                            │
  │                                            │
  └───────────────────────────────────────────┘

5.2 元数据更新流程

当表结构变化时(如 DDL),元数据如何传播:

DDL: ALTER TABLE users ADD COLUMN phone VARCHAR(20);

  1. TiDB Server 执行 DDL
     │
     v
  2. 将 DDL 变更写入 TiKV(通过 distributed DDL 协议)
     │
     v
  3. PD 感知到元数据变更
     │
     v
  4. PD 通知所有 TiDB Server 更新缓存
     │
     v
  5. TiDB Server 加载最新元数据
     │
     v
  6. DDL 完成,客户端可以继续操作

在线 DDL 不会阻塞 DML(数据读写),这是因为 TiDB 使用了类似 gh-ost 的在线 schema 变更算法。


六、查看集群状态实践

6.1 通过 SQL 查看集群信息

-- 查看各组件信息
SELECT * FROM information_schema.cluster_info;
-- 输出包含组件类型、版本、地址、启动时间等
+------+-----------------+-----------------+---------+------------------------------------------+---------------------+------------------+-----------+
| TYPE | INSTANCE        | STATUS_ADDRESS  | VERSION | GIT_HASH                                 | START_TIME          | UPTIME           | SERVER_ID |
+------+-----------------+-----------------+---------+------------------------------------------+---------------------+------------------+-----------+
| tidb | 127.0.0.1:4000  | 127.0.0.1:10080 | 8.5.0   | d13e52ed6e22cc5789bed7c64c861578cd2ed55b | 2026-05-21 16:36:45 | 40m52.372333026s |      1385 |
| tidb | 127.0.0.1:4001  | 127.0.0.1:10081 | 8.5.0   | d13e52ed6e22cc5789bed7c64c861578cd2ed55b | 2026-05-21 16:36:45 | 40m52.372340986s |       640 |
| pd   | 127.0.0.1:2382  | 127.0.0.1:2382  | 8.5.0   | d190c0e9082de46128b756f93b1291768dda645a | 2026-05-21 16:36:06 | 41m31.372343796s |         0 |
| pd   | 127.0.0.1:2379  | 127.0.0.1:2379  | 8.5.0   | d190c0e9082de46128b756f93b1291768dda645a | 2026-05-21 16:36:06 | 41m31.372349536s |         0 |
| pd   | 127.0.0.1:2384  | 127.0.0.1:2384  | 8.5.0   | d190c0e9082de46128b756f93b1291768dda645a | 2026-05-21 16:36:06 | 41m31.372351846s |         0 |
| tikv | 127.0.0.1:20160 | 127.0.0.1:20180 | 8.5.0   | a2c58c94f89cbb410e66d8f85c236308d6fc64f0 | 2026-05-21 16:36:21 | 41m16.372354186s |         0 |
| tikv | 127.0.0.1:20161 | 127.0.0.1:20181 | 8.5.0   | a2c58c94f89cbb410e66d8f85c236308d6fc64f0 | 2026-05-21 16:36:21 | 41m16.372358676s |         0 |
| tikv | 127.0.0.1:20162 | 127.0.0.1:20182 | 8.5.0   | a2c58c94f89cbb410e66d8f85c236308d6fc64f0 | 2026-05-21 16:36:21 | 41m16.372360926s |         0 |
+------+-----------------+-----------------+---------+------------------------------------------+---------------------+------------------+-----------+

-- 查看 TiKV Store 状态
SELECT
    STORE_ID,
    ADDRESS,
    STORE_STATE,
    CAPACITY,
    AVAILABLE,
    LEADER_COUNT,
    REGION_COUNT
FROM information_schema.tikv_store_status;

+----------+-----------------+-------------+----------+-----------+--------------+--------------+
| STORE_ID | ADDRESS         | STORE_STATE | CAPACITY | AVAILABLE | LEADER_COUNT | REGION_COUNT |
+----------+-----------------+-------------+----------+-----------+--------------+--------------+
|        5 | 127.0.0.1:20162 |           0 | 925GiB   | 843.3GiB  |           21 |           79 |
|        1 | 127.0.0.1:20160 |           0 | 925GiB   | 843.3GiB  |           28 |           79 |
|        4 | 127.0.0.1:20161 |           0 | 925GiB   | 843.3GiB  |           30 |           79 |
+----------+-----------------+-------------+----------+-----------+--------------+--------------+

-- 查看 Region 分布
SELECT
    REGION_ID,
    START_KEY,
    END_KEY,
    APPROXIMATE_SIZE,
    APPROXIMATE_KEYS
FROM information_schema.tikv_region_status
LIMIT 10;

6.2 通过 PD API 查看

# 查看集群状态
curl http://127.0.0.1:2379/pd/api/v1/health

# 查看 Store 信息
curl http://127.0.0.1:2379/pd/api/v1/stores

# 查看 Region 统计
curl http://127.0.0.1:2379/pd/api/v1/regions

# 查看集群配置
curl http://127.0.0.1:2379/pd/api/v1/config

6.3 通过 TiDB Dashboard 查看

访问 http://<tidb-server>:2379/dashboard 可以打开 TiDB Dashboard,提供:

  • 集群概况(QPS、延迟、存储使用率)
  • Top SQL 分析
  • 慢查询分析
  • 可视化流量
  • 集群诊断
  • 日志搜索

七、理解数据分布与热点

7.1 什么是写入热点

当大量写入集中在某个 Region 时,该 Region 所在的 TiKV 节点会成为性能瓶颈:

TiKV Node 1  ━━━━━ Region A (热点)  ← 大量写入打到这里
TiKV Node 2  ━━━━━ Region B        ← 空闲
TiKV Node 3  ━━━━━ Region C        ← 空闲

7.2 热点产生的原因

最常见的原因是单调递增的主键

CREATE TABLE logs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,  -- 连续递增
    message TEXT
);

由于数据按 Key 范围分布到 Region,连续递增的主键会让所有新数据都写入最后一个 Region:

Region A: [1, 1000]     → 不再写入
Region B: [1001, 2000]  → 不再写入
Region C: [2001, 3000]  → 所有新写入都集中在这里!热点!

7.3 如何避免热点

方案一:使用 AUTO_RANDOM(推荐)

CREATE TABLE logs (
    id BIGINT PRIMARY KEY AUTO_RANDOM,  -- 自动打散
    message TEXT
);

AUTO_RANDOM 将自增值编码后分散到整个 Key 空间,写入自然分散到不同 Region。

方案二:Shard Row ID Bits

CREATE TABLE logs (
    id BIGINT AUTO_INCREMENT,
    message TEXT
) SHARD_ROW_ID_BITS = 4 PRE_SPLIT_REGIONS = 4;

这种方式会打散内部 Row ID 的分布。

方案三:业务层面打散

-- 使用 UUID 或雪花 ID 作为主键
-- 在应用层生成全局唯一 ID

八、小结

深入解析了 TiDB 的三大核心组件:

  • TiDB Server:无状态计算层,负责 SQL 解析、优化和执行
  • PD Server:集群大脑,管理元数据、调度、分配全局时间戳
  • TiKV Server:分布式存储引擎,基于 Region + Raft + MVCC 实现高可用

0
0
1
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论