TiDB 2020 — 给 Raft 加一个"旁听生",让一份数据同时跑事务和分析
是什么
TiDB 是一个让你只用一个数据库,就能既跑业务交易、又跑数据分析的分布式系统。它的核心招式叫 HTAP(Hybrid Transactional and Analytical Processing,混合事务+分析处理)。
日常类比:以前公司有”白天柜台”和”晚上财务”两个系统——白天柜台收钱(事务),晚上把数据导到财务系统跑报表(分析)。问题是报表看到的总是”昨天的数据”。TiDB 的做法相当于:在柜台旁边坐一个旁听员,柜台每写一笔单据他就抄一份成”报表友好格式”,财务直接读他这份,不用等导出。
技术层面这个旁听员叫 Raft learner(学习者节点):不投票、不参与多数派、单纯异步复制日志。柜台叫 TiKV(行存),旁听员叫 TiFlash(列存),调度官叫 PD(Placement Driver)。
-- 用户视角和 MySQL 一模一样SELECT count(*) FROM orders WHERE city = 'shanghai';-- 优化器自动决定走 TiKV(行存索引)还是 TiFlash(列存全扫)为什么重要
不理解 TiDB 这套设计,下面这些事都说不清:
- 为什么”实时分析”在 2010 之前都做不到——纯靠 ETL 同步至少滞后几小时
- 为什么 Snowflake / PolarDB / 后来很多 HTAP 系统都抄了”日志异步出列存副本”思路
- 为什么 NewSQL(CockroachDB / Spanner)和 HTAP 不是一回事——前者只解决 OLTP 扩展性
- 为什么 PingCAP 这家中国创业公司能把 VLDB 论文写成产品落地,而不是相反
核心要点
TiDB 把 HTAP 拆成三个工程问题,每个都有专门答案:
-
新鲜度(freshness)——分析要看到刚写的数据。靠 Raft learner 把日志异步拉到列存,延迟 < 100ms(论文实测),不是几小时的 ETL。类比:旁听员就坐在柜台旁,写完立刻抄。
-
隔离性(isolation)——分析查询不能拖慢交易。把列存(TiFlash)部署在独立机器上,CPU/内存/磁盘都和行存(TiKV)分开。类比:旁听员有自己的桌子,柜台不会被他翻账本翻慢。
-
一致性(consistency)——读列存和读行存得到同一份事实。靠 learner 强一致协议:读 TiFlash 时先去问 leader 当前 commit index,等本地 apply 到这个点再返回。
三件事合起来构成 “Raft learner = 把共识算法从 ‘只为高可用’ 升级成 ‘高可用 + HTAP 副本’” 这一篇论文的全部贡献。
实践案例
案例 1:MySQL 协议接进来,自动分片
-- 客户端连 TiDB 就像连 MySQLmysql -h tidb-server -P 4000 -u root> CREATE TABLE orders (id BIGINT PRIMARY KEY, city VARCHAR(20), amount INT);> INSERT INTO orders VALUES (1, 'shanghai', 100);> SELECT * FROM orders WHERE id = 1;逐部分解释:
- 数据按主键
id范围切成多个 Region(默认每个 96MB),分布在不同 TiKV 节点 - PD 自动决定哪个 Region 放哪台机器,hot Region 还会主动 split / 迁移
- 用户完全感觉不到分片,写一条 SQL 就行,这是 NewSQL 的核心承诺
案例 2:Raft learner 异步同步行转列
[Leader (TiKV 行存)] 写入 row: {id=1, city=shanghai, amount=100} → Raft log entry 写入 → 同步到 follower(多数派提交)→ 用户拿到 ack → 异步推给 learner (TiFlash) ↓ 收到 log,转成列格式: id 列: [1, ...] city 列: [shanghai, ...] amount 列:[100, ...]关键点:learner 不计入 quorum,所以它慢了不会拖累事务延迟;但 leader 会持续推日志给它,平均 < 100ms 后列存就有这条数据。
案例 3:优化器同时用行存和列存
SELECT T.*, S.a FROM T JOIN S ON T.b = S.b WHERE T.a BETWEEN 1 AND 100;TiDB CBO(基于代价的优化器)会算三条路径的成本:
- T 走 TiKV 索引扫(因为 a BETWEEN 1 AND 100 范围窄,命中行少)
- S 走 TiFlash 列扫(因为只要两列 a / b,列存压缩比高、扫得快)
- 最后在 SQL 层做 hash join
这就是论文最妙的地方:因为两边数据一致,优化器可以混搭行存和列存路径,而不是非此即彼。
踩过的坑
-
列存延迟不是 0:learner 异步拉日志再做行转列,重写入压力下能到 100-300ms 滞后。如果业务要”刚写完立刻在 BI 报表里看到”,要在 SQL 里强制
SET tidb_read_staleness=0等同步,会把延迟暴露给查询。 -
Region 太热 split 不动:默认 96MB 一个 Region,热点 key(比如自增 ID 永远写最新那个 Region)全压在一个 Raft leader 上。要么改 schema 用 hash 前缀打散,要么靠 PD 提前 pre-split,否则单 leader 写入瓶颈。
-
PD 是单点逻辑(虽然多副本):所有事务都要找 PD 拿 timestamp(时间戳),PD 集群挂了整个数据库停写。生产部署必须 3 节点 PD,且不能离 SQL 层网络太远——每次事务一次 RPC 累计起来不便宜。
-
跨 Region 大事务慢:Percolator 风格 2PC(两阶段提交)跨多个 Region 时要做两轮 RPC(prewrite + commit),写入键多了延迟成倍涨。OLTP 设计上要避免一个事务横跨太多 Region。
适用 vs 不适用场景
适用:
- 业务既要做事务(订单、库存)又要看实时报表(销售看板、风控)——一份数据避免 ETL
- 数据量超过单机 MySQL 上限(>1TB),又不想拆库拆表手动管
- 需要 MySQL 协议兼容(业务代码不重写)+ 水平扩展能力
- 报表延迟从 “T+1” 降到 “秒级”,对业务有真实价值
不适用:
- 纯 OLTP 极限延迟(<1ms)→ 选 aurora / 单机 MySQL,TiDB 走 Raft 多数派固有 ms 级延迟
- 纯 OLAP 大查询批跑 → 选 bigtable-2006 / Snowflake,TiDB 列存为”鲜度优先”不是”扫描极致”
- 跨数据中心强一致 → 选 spanner-2012(TrueTime),TiDB 跨 DC 部署延迟翻倍
- 强一致全局快照(多区一次性看同一时刻)→ Spanner / calvin-2012 更专门
历史小故事(可跳过)
- 2015 年:PingCAP 在北京创立,目标做开源 NewSQL;早期只有 TiKV(用 Rust 写、靠 Raft 复制),还没列存
- 2017 年:TiDB 1.0 GA,主打 “MySQL 协议 + 水平扩展”,但分析查询慢得没法用
- 2018-2019 年:试过 “TiSpark 直连 TiKV 跑分析”,行存扫起来仍然慢;意识到必须有列存
- 2020 年:这篇 VLDB 论文公开 TiFlash + Raft learner 方案,这是中国 NewSQL 第一篇上 VLDB 的工业论文
- 之后:Snowflake 的 Hybrid Tables、PolarDB-IMCI、Yugabyte 都借鉴 “log-based 异步列存副本” 思路
学到什么
- 共识算法不只是为了高可用——Raft 多副本本来是防故障,TiDB 把它升级成”防故障 + 出 HTAP 副本”,思路迁移性极强
- 异步 ≠ 不一致——learner 拉日志虽然异步,但读时强制等 apply,外部观察到的仍是强一致
- NewSQL → HTAP 是自然演化——任何已经在做共识复制的系统都可以走这条路,不用从头造
- 代价模型决定一切——同一份数据有行存和列存两条路,优化器代价模型必须能选对,否则架构白搭
延伸阅读
- TiDB 官方文档(中英文都有,零基础友好)
- VLDB 2020 论文 PDF(13 页,2 / 4 / 5 节最值得读)
- Raft 原论文——理解 learner 之前先理解 leader / follower
- Percolator 论文——TiDB 的 2PC 实现源头
- raft —— learner 角色就是从 Raft 扩出来的
关联
- raft —— TiDB 的复制协议基础,learner 是它加的角色
- paxos-1998 —— 比 Raft 老一辈的共识算法,理论上等价但难实现
- spanner-2012 —— 同代竞品,靠 TrueTime 做跨 DC 强一致;TiDB 选了”单 DC HTAP”路线
- cockroachdb-2020 —— 同期 NewSQL,纯 OLTP 路线,论文做过对比
- f1-2013 —— Spanner 上的 SQL 引擎,是 TiDB SQL 层灵感来源之一
- rocksdb-lsm —— TiKV 底层存储引擎
- bigtable-2006 —— TiKV 的 key-value 模型源头
反向链接
- aurora —— Aurora — 把数据库的下半身换成日志机
- bigtable-2006 —— Bigtable 2006 — Google 把行级随机读写做到 PB 级的存储系统
- calvin-2012 —— Calvin 2012 — 先排好顺序再执行,让跨分区事务不再走 2PC
- cockroachdb-2020 —— CockroachDB 2020 — 没原子钟也能做全球强一致 SQL 数据库
- paxos-1998 —— Paxos 1998 — 古希腊议会寓言里藏的共识协议
- raft —— Raft — 易理解的共识算法
- rocksdb-lsm —— LSM-tree 与 RocksDB — 把所有写都变成顺序写
- spanner-2012 —— Spanner 2012 — 用原子钟和 GPS 给全球数据库发时间戳
- vitess —— Vitess — 给 MySQL 装上水平分片的代理层