从前有家叫“小同”的面馆,生意红火到排队排到了马路对面。老板想开分店,却撞上一堵墙:单店后厨和收银台绑在一起,店一多账就乱,食材也不够用。于是他做了一个大胆的决定——把“做饭”和“存菜”彻底分开:点单员和厨师只负责接单炒菜(计算),食材统一送进冷链仓库(存储),又请了一位运营经理管着所有仓库的货架台账(调度)。分店想开多少就开多少,顾客点菜的方式却一点没变。这套“连锁餐饮集团”的玩法,正是 TiDB 的架构灵魂。TiDB 是 NewSQL 阵营的明星,它保留了人们熟悉的 MySQL 点菜方式(协议兼容),骨子里却是分布式系统的筋骨——可以像乐高一样水平扩展,还能在营业的同时顺手把经营报表算出来。接下来,我们就走进这家“数据库连锁集团”,看看它的后厨、仓库和调度台是怎么协作的。

岗位关联

理解 TiDB 整体架构,是数据库工程师迈向分布式存储世界的必修课。它能帮你快速上手真实工业级系统,也能让你在架构选型、性能调优和故障排查时,像看自家厨房一样看清每个环节。

目录
  1. 可视化演示
  2. 系统总览
  3. TiDB计算层
  4. TiKV存储层
  5. PD调度中心
  6. Region与Raft
  7. TiFlash列存
  8. 核心流程
  9. 架构价值
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

系统总览

小同的面馆升级为连锁集团后,立下三条规矩:第一,点单员和厨师只管接待,不管仓库;第二,食材全部放进冷链仓库,按箱分装,三仓各存一份;第三,运营经理手里攥着一本总台账,谁家缺货、谁家爆仓,都由他来调配。这套规矩,就是 TiDB 的顶层设计——计算存储分离。计算的人不碰数据,存储的人不碰业务,各司其职才能各展所长。

把面馆翻译成数据库:TiDB 集群由三种角色组成。TiDB 节点是前台的“点单员+厨师”,负责解析 SQL、生成执行计划,然后把活儿派下去;TiKV 是“冷链仓库”,以键值对(Key-Value)形式把数据分片存在多台机器上;PD 是“运营经理”,既管着数据分片的位置台账,又负责故障发现与负载调度。三者合在一起,对外却依然透出 MySQL 的熟悉味道——老顾客用的菜单、点菜的习惯全都不用改,这正是 TiDB 兼容 MySQL 协议的杀手锏。

这套架构带来最直观的好处是水平扩展:面馆生意火爆,与其把单店后厨扩到无限大,不如多开几家分店。TiDB 里加一台 TiDB 节点就多一份并发处理能力,加一台 TiKV 节点就多一份存储容量,理想情况下集群规模翻倍,吞吐量也接近翻倍。

当然,真实系统里网络开销、元数据同步会让这条曲线打点折扣,但 TiDB 的方向正是让 $T_{\text{总}}$ 尽量贴近这条理想直线。面馆的规矩立住了,后面的故事都从这三条规矩展开。

T总=n×T单\displaystyle T_{\text{总}} = n \times T_{\text{单}}
  1. 单节点吞吐$T_{\text{单}}$ 表示一个 TiDB 节点每秒能处理的请求量,类似一家分店单日能接待的客人。
    T单T_{\text{单}}
  2. 节点数量$n$ 表示水平扩展后计算节点的总个数,相当于连锁集团开的分店数。
    nn
  3. 规模叠加理想状态下,总吞吐 $T_{\text{总}}$ 等于单节点吞吐乘以节点数,说明加人就能加量,这就是线性扩展的直观含义。
    T总=n×T单T_{\text{总}} = n \times T_{\text{单}}

TiDB计算层

走进连锁集团的前厅,你会看见一群手脚麻利的点单员和服务员。客人报出一串菜名,点单员要听懂需求(SQL 解析),然后心里快速盘算:是先煮面还是先备料?需不需要让后厨并行出菜?(执行计划优化)这些前厅人员有个特点——他们身上从不带食材,下班拍拍手就能去任何一家分店顶班,这就是“无状态”。

在 TiDB 中,TiDB Server 就是这群前厅人员。一条 SQL 进来,第一步是解析:把“我要一碗牛肉面”这样的自然语言拆成语法树,检查语法对不对、字段存不存在。第二步是优化:生成多个执行计划,挑成本最低的那个。第三步是执行:把任务拆成子任务,分发给 TiKV 集群并行处理,最后汇总成结果端给客户端。整个过程对应用透明,应用以为自己只在跟一台 MySQL 说话,实际上背后有一整支分布式团队在协作。

无状态设计是 TiDB 计算层最优雅的地方。因为不存数据,TiDB 节点挂了,新节点秒级顶上,业务几乎无感;因为不存数据,TiDB 节点可以随意加减,前端接入层挂一个负载均衡,就能把请求均匀地分给所有活着的节点。集群的查询能力不再是单机的天花板,而是一群节点能力的叠加。

前厅团队越招越多,接待能力越来越强,但他们始终有个原则:只管接单传菜,绝不去仓库搬货。接下来,我们跟着食材,走进冷链仓库看看。

Q集群=∑i=1mqi\displaystyle Q_{\text{集群}} = \sum_{i=1}^{m} q_i
  1. 单机查询能力$q_i$ 表示第 $i$ 台 TiDB 节点每秒能处理的查询数(QPS),相当于一个点单员在一小时内能接待的客人。
    qiq_i
  2. 节点遍历$\sum_{i=1}^{m}$ 表示把第 1 台到第 $m$ 台节点的能力全部加起来,这m台就是前厅所有的点单员。
    ∑i=1m\sum_{i=1}^{m}
  3. 集群总能力$Q_{\text{集群}}$ 是整支点单团队的总接待能力,体现了无状态节点水平扩展带来的线性收益。
    Q集群=∑i=1mqiQ_{\text{集群}} = \sum_{i=1}^{m} q_i

TiKV存储层

冷链仓库是连锁集团的命脉。为了让食材存取高效,仓库实行“编号定位”:每种食材对应一个编号(Key),编号后面跟着存放位置与数量(Value),一张巨大的键值对照表就是全部家当。这就是 TiKV 的存储模型——一份分布式的 Key-Value 大表,只不过这张表被拆成了无数个小格子,铺在多台机器的磁盘上。

每个小格子,在 TiDB 里叫 Region。Region 是一段连续 Key 范围的数据,默认约 96MB,就像仓库里统一规格的周转箱。为什么要用箱子?因为整仓库找一件食材太慢,按编号段分区后,找食材只要先定位到箱子,再在箱子内翻找。更重要的是,箱子是数据调度和复制的基本单位:每个箱子在三个不同的仓库里各存一份,某间仓库失火,另外两间还能供货。

TiKV 的 Key 并不是简单的业务主键,它在内部会做一层编码,把表 ID、行 ID 等信息拼装进 Key,让同一张表的数据在物理上连续存放,扫描时不用跨箱子乱翻。

有了箱子,有了编号,还缺一个关键角色:谁来记下每个箱子放在哪个仓库的哪个货架?这个角色,就是 PD。

K=P表前缀+ID表+P行前缀+ID行\displaystyle K = P_{\text{表前缀}} + ID_{\text{表}} + P_{\text{行前缀}} + ID_{\text{行}}
  1. 表前缀$P_{\text{表前缀}}$ 是固定字节,用来标识这是表数据的起始标记,相当于仓库里“生鲜区”的区位标识。
    P表前缀P_{\text{表前缀}}
  2. 表标识$ID_{\text{表}}$ 是全局唯一的表编号,每张表对应一个 ID,用来区分不同表的数据。
    ID表ID_{\text{表}}
  3. 行前缀与行号$P_{\text{行前缀}}$ 表示行数据标记,$ID_{\text{行}}$ 是这一行在表内的唯一编号。四段拼接后,相同表的数据 Key 前缀一致,天然聚在一起。
    P行前缀+ID行P_{\text{行前缀}} + ID_{\text{行}}

PD调度中心

连锁集团的运营经理老周,手上有三样宝贝:一本总台账、一部对讲机、一只盖章机。总台账记着每个箱子(Region)目前在哪个仓库、哪一排货架;对讲机让他每天定时和每个仓库通话,确认“一切正常”;盖章机则是给每笔跨仓业务盖一个统一的时间章,保证所有仓库按同一个时间线记账。

在 TiDB 中,老周就是 PD(Placement Driver,调度中心)。PD 维护的路由元数据,是客户端和 TiDB 节点找到数据的导航地图。TiKV 节点会通过心跳持续向 PD 汇报状态:我活着、我还有多少箱子、我负载多高。一旦某节点心跳超时,PD 立刻判定节点失联,启动副本补足流程。PD 还像一位精明的调度员,发现某台 TiKV 的 Region 特别多、成为热点时,就把一部分箱子搬到空闲节点,让整个集群的负载像水面一样平。

怎么衡量“均匀”?PD 用一个平方和公式作为不均衡度的度量:每个节点 Region 数量的平方加起来,值越小越均衡。这种度量让“大的更大”被放大,从而逼着调度器优先处理最忙的节点。

老周的台账、对讲机和盖章机,让整个集群始终处于“有人管”的状态。但你可能好奇:台账上写着一个箱子有三个副本,这三个副本之间是怎么保持一致的呢?这就是 Region 与 Raft 协议的故事。

L=∑i=1nni2\displaystyle L = \sum_{i=1}^{n} n_i^2
  1. 节点Region数$n_i$ 表示第 $i$ 台 TiKV 节点上存放的 Region 数量,也就是这家仓库的箱子数。
    nin_i
  2. 平方放大$n_i^2$ 对箱子数取平方,让箱子多的节点数值更大,突出“不均衡”的严重程度。
    ni2n_i^2
  3. 求和度量$\sum_{i=1}^{n}$ 把所有节点的平方值加总得到 $L$,$L$ 越小说明整体越均衡,PD 的调度目标就是让 $L$ 不断下降。
    L=∑i=1nni2L = \sum_{i=1}^{n} n_i^2

Region与Raft

三兄弟合伙开仓库,最怕的就是账目对不上。老周定的规矩是:每一箱食材入库时,必须至少两个兄弟在入库单上签字画押,这箱货才算正式入库;如果三个兄弟都签了,那最好,但缺一个也不耽误收活。签字画押的过程还要写进一本只追加、不修改的流水账本里,方便日后对账。这套制度,就是著名的 Raft 一致性协议。

Region 的每个副本分布在不同的 TiKV 节点上,其中一个被选为 Leader(主副本),负责接收读写请求;另外两个是 Follower(从副本),负责同步日志。当业务要写一条数据时,Leader 把日志广播给 Follower,只要收到多数派(三个里两个)的确认,就可以向客户端返回成功。这样即使一个 Follower 节点宕机,集群依然能正常写入,这就是高可用的底气。

更妙的是,Region 的大小不是写死的。数据越写越多,箱子装满了(超过约 96MB),Region 会自动分裂成两个,像细胞分裂一样;反过来,如果删除数据导致箱子太瘪,相邻 Region 又会合并。PD 正是靠着这套分裂与合并机制,让数据分布始终保持在健康状态。

三兄弟的账本制度,让每一箱食材都有据可查、有错可纠。但面馆越来越大,老周发现了一个新问题:老板每天要的“经营报表”越来越多,可报表查询要是翻流水账,前台就没法做生意了。于是,集团专门成立了一个分析师团队。

N=2F+1,W=F+1\displaystyle N = 2F + 1, \quad W = F + 1
  1. 故障上限$F$ 表示集群允许同时宕机的节点数量,对应仓库三兄弟里“最多失联几人还能正常营业”。
    FF
  2. 副本总量$N = 2F + 1$ 表示要容忍 $F$ 个故障,至少要准备 $2F+1$ 个副本。三副本时 $F=1$,可以容忍一台机器宕机。
    N=2F+1N = 2F + 1
  3. 写入确认数$W = F + 1$ 表示写入时最少需要多少个副本确认才提交。三副本时 $W=2$,即多数派确认,既保证安全又不必等最慢的那个。
    W=F+1W = F + 1

TiFlash列存

分析师团队的办公室里,没有锅碗瓢盆,只有一排排特制的大账本。普通仓库的流水账(行存)是按“时间逐笔”记录的,查某一天的交易很快;分析师的账本(列存)则是按“食材名称”整列记录的——“番茄”这一列记着全年每天用了多少斤。老板问“今年番茄用了多少”,分析师扫一眼那一列就算完了,而普通仓库得把整本流水翻个遍。

这排特制账本,就是 TiFlash。TiFlash 是 TiDB 家族里的列存引擎,专门服务分析型查询(OLAP)。它不参与前台收银的主链路,而是以 Raft Learner(学习副本)的身份悄悄旁听每一笔账目,异步复制 TiKV 中的数据。Learner 不参与投票,因此它的同步完全不影响前台交易性能——分析师抄账本归抄账本,收银台该多快还是多快。

TiFlash 就位后,TiDB 就成了一家“既做快餐又出报表”的两栖企业,这就是 HTAP(混合事务与分析处理)。行存服务高频交易,列存服务复杂分析,两边数据天然一致,省去了传统架构里“交易库到分析库的 ETL 搬运”,分析结果实时性大幅提升。对比一下全表扫描的代价,列存的优势一目了然:

分析师团队就位后,连锁集团的“营业”与“经营”两条线互相独立又彼此印证。现在,让我们从头到尾走一遍一笔真实的订单,看看所有角色如何串成一条流水线。

IO行=R×C×B,IO列=R×c×B\displaystyle IO_{\text{行}} = R \times C \times B, \quad IO_{\text{列}} = R \times c \times B
  1. 行数$R$ 表示表中的总行数,比如全年 36.5 万笔交易记录。
    RR
  2. 列数与字节$C$ 是表的全部列数,$B$ 是每列平均字节数;$c$ 是分析查询真正需要的列数,通常远小于 $C$。
    C,  c,  BC, \; c, \; B
  3. IO对比$IO_{\text{行}} = R \times C \times B$ 是行存全表扫描要读的数据量,$IO_{\text{列}} = R \times c \times B$ 是列存只读目标列的数据量。当 $c \ll C$ 时,列存 IO 大幅缩减,这就是分析加速的秘密。
    IO行=R×C×B,IO列=R×c×BIO_{\text{行}} = R \times C \times B, \quad IO_{\text{列}} = R \times c \times B

核心流程

一位顾客走进面馆,喊了一声:“来一碗牛肉面,多加香菜!”接下来发生的事,就是 TiDB 的一次完整写入流程。点单员(TiDB 节点)接到 SQL,先解析语义,然后找老周(PD)查台账:牛肉面的食材记录在哪个 Region、主副本在哪个仓库?老周秒回路由信息。点单员直奔目标 TiKV 节点,主副本(Leader)把这条记录写进日志,同步给两个从副本,收到多数派点头后,一份热腾腾的“写入成功”就端给了顾客。

整个过程看似简单,背后却有一项关键保障:分布式事务。面馆做的是“牛肉面套餐”,面条和牛肉必须一起上桌,不能只成功一半。TiDB 采用两阶段提交(2PC):先让所有相关 Region 的 Leader 把写入操作“预备”好,等全部确认后,再统一“提交”生效。任何一步失败,整笔订单回滚,就像后厨发现牛肉不够,整份套餐就不出餐,绝不上半碗面。

这套流水线同样支持弹性运维。想扩容,新加一台 TiKV,PD 会自动搬一批 Region 过去;某台 TiKV 宕机,PD 心跳超时后,其他副本立刻补位成为新 Leader,业务在秒级内恢复。所有操作对前台顾客无感——他们只觉得这家店“永远在线、从不打烊”。

从接单到上菜,整套流水线环环相扣。最后,我们退后一步,看看这家“数据库连锁集团”在整个行业里到底值多少钱。

T2PC=2×RTT\displaystyle T_{\text{2PC}} = 2 \times RTT
  1. 单程通信时延$RTT$ 表示客户端与数据库之间一个网络往返的时延,类似点单员跑一趟仓库的时间。
    RTTRTT
  2. 预备阶段第一个 $RTT$ 对应两阶段提交的 prepare 阶段,所有参与节点先“备好菜但不下锅”。
    RTT1=RTTRTT_1 = RTT
  3. 提交阶段第二个 $RTT$ 对应 commit 阶段,所有备好的菜同时“出锅上桌”。两个往返合计就是分布式事务的基本时延开销。
    T2PC=2×RTTT_{\text{2PC}} = 2 \times RTT

架构价值

当初的小面馆,如今成了遍布全国的连锁集团。它的成功密码可以总结为四句话:不怕单店倒闭(高可用)、账本从不扯皮(强一致)、分店想开就开(线性扩展)、老顾客无缝迁移(生态兼容)。这四点,正是 TiDB 作为 NewSQL 代表在工业界站稳脚跟的根基。

高可用来自多副本与 Raft:任意一台机器宕机,数据不丢、服务不停。强一致来自 Raft 日志复制与分布式事务:多副本不会出现“各说各话”的脏读。线性扩展来自计算存储分离与 Region 自动调度:加机器就能同时加容量和性能。生态兼容来自 MySQL 协议:应用层 ORM、运维工具、监控体系几乎零改造接入,企业迁移成本极低。

衡量这套体系“有多可靠”,有一个朴素而有力的公式。系统的可用性取决于平均无故障时间(MTBF)和平均修复时间(MTTR):出故障不可怕,怕的是修得慢;自动化故障转移正是把 MTTR 从小时级压到秒级的关键。

小同的面馆故事讲完了:前厅接单、仓库存货、经理调度、分析师算账,各司其职又浑然一体。TiDB 之所以能成为 NewSQL 的标杆,正是因为它把分布式系统的复杂藏在了熟悉的 MySQL 面孔之下。理解了这套整体架构,你就拿到了走进分布式数据库世界的第一把钥匙。

A=MTBFMTBF+MTTR\displaystyle A = \frac{MTBF}{MTBF + MTTR}
  1. 无故障时间$MTBF$ 是系统平均能连续运行多久才出一次故障,相当于面馆平均多久才遇到一次停电。
    MTBFMTBF
  2. 修复时间$MTTR$ 是出故障后平均花多久恢复,相当于停电后多久恢复供电。
    MTTRMTTR
  3. 可用性比率$A$ 是两者之比,分子是“健康时长”,分母是“健康+生病总时长”。MTTR 越小,$A$ 越接近 1,也就是全年 99.99% 的时间都在营业。
    A=MTBFMTBF+MTTRA = \frac{MTBF}{MTBF + MTTR}

AI 应用场景

电商大促订单中心的实时分析

阶段:数据存储与特征服务

解决的问题:传统电商用 MySQL 分库分表,大促时订单写入暴增,扩容靠人工拆表,且分析报表要等凌晨 ETL,老板看不到实时 GMV。TiDB 的计算存储分离让扩容变成“加机器”;TiFlash 列存让“边交易边分析”成为可能,分析查询在列存副本上跑,不抢交易写入的 CPU。

具体案例:某头部电商在大促期间将订单库迁到 TiDB,3 个 TiDB 节点 + 5 个 TiKV 节点动态扩展到 10 个 TiDB 节点 + 15 个 TiKV 节点,QPS 翻了三倍;运营大屏的实时 GMV 查询直接打到 TiFlash,秒级出数,不再等待 T+1 报表。

金融风控系统的跨分片事务查询

阶段:特征存储与在线推理支撑

解决的问题:风控规则引擎需要同时查询用户基本信息、交易流水、设备指纹等多类数据,数据天然分散。传统方案要么引入分布式事务中间件,要么牺牲一致性。TiDB 原生分布式事务让跨 Region 的读写在分布式下依然 ACID,PD 的负载均衡则避免热点用户的数据集中打爆某台机器。

具体案例:某消费金融公司风控系统每天处理百万级进件,每个进件要读取 20 余项特征。接入 TiDB 后,风控决策时延稳定在 100ms 内,PD 自动把高频用户的 Region 分散到多节点,告别了单点热点导致的超时告警。

AI应用元数据与特征存储底座

阶段:AI 应用的数据基座

解决的问题:AI 推荐、画像系统动辄需要存储上亿用户的特征元数据与中间结果,同时要支撑高并发点查。TiDB 兼容 MySQL 协议,AI 团队用熟悉的 ORM 即可接入,不必为数据访问单独造轮子;计算存储分离让特征表可以随业务增长平滑扩容。

具体案例:某短视频推荐团队将用户画像标签、物品元数据存入 TiDB,支撑每日数十亿次 key-value 点查;向量检索部分交给专用向量数据库,TiDB 负责结构化特征与事务性元数据,两者配合构成完整的 AI 数据底座。

Python 演示

python100 行
"""mini_tidb.py:简化版TiDB架构模拟器"""REGION_MAX = 50  # 每个Region最多容纳50条记录  class Region:    """一个Region = 一段连续Key范围的数据分片"""     def __init__(self, rid, start, end):        self.rid = rid        self.start = start        self.end = end        self.data = {}     def in_range(self, key):        return self.start <= key < self.end     def put(self, key, value):        self.data[key] = value        return len(self.data) >= REGION_MAX  # 超容量返回True  class TiKVNode:    """存储节点,对应一个TiKV实例"""     def __init__(self, node_id):        self.node_id = node_id        self.regions = []     def count(self):        return len(self.regions)  class PD:    """调度中心:维护路由,执行分裂与负载均衡"""     def __init__(self, nodes):        self.nodes = nodes        self.route = {}  # rid -> node_id     def assign(self, region):        """把Region放到当前Region最少的节点并登记路由"""        node = min(self.nodes, key=lambda n: n.count())        node.regions.append(region)        self.route[region.rid] = node.node_id        return node     def route_to(self, key):        """根据Key找到对应Region和所在节点"""        for node in self.nodes:            for r in node.regions:                if r.in_range(key):                    return r, node        raise KeyError("no region for key: %s" % key)     def split(self, r):        """Region分裂:一半数据留给原Region,一半交给新Region"""        keys = sorted(r.data)        mid = len(keys) // 2        split_key = keys[mid]        new_r = Region(len(self.route) + 1, split_key, r.end)        r.end = split_key        for k in keys[mid:]:            new_r.data[k] = r.data.pop(k)        target = self.assign(new_r)        print("[分裂] Region%d -> Region%d, 新Region放到TiKV%d"              % (r.rid, new_r.rid, target.node_id))     def balance(self):        """把Region最多的节点上的Region迁到最少的节点"""        src = max(self.nodes, key=lambda n: n.count())        dst = min(self.nodes, key=lambda n: n.count())        if src.count() - dst.count() >= 2:            moved = src.regions.pop()            dst.regions.append(moved)            self.route[moved.rid] = dst.node_id            print("[调度] Region%d从TiKV%d迁到TiKV%d"                  % (moved.rid, src.node_id, dst.node_id))  def main():    nodes = [TiKVNode(1), TiKVNode(2), TiKVNode(3)]    pd = PD(nodes)    pd.assign(Region(1, 0, 10000))     for key in range(200):        r, node = pd.route_to(key)        need_split = r.put(key, "value_%d" % key)        if need_split:            pd.split(r)            if key % 40 == 0:                pd.balance()     print("最终Region分布:")    for n in nodes:        rids = [r.rid for r in n.regions]        print("TiKV%d: %d个Region %s" % (n.node_id, n.count(), rids))  if __name__ == "__main__":    main()
目的:用约百行代码模拟 TiDB 的 Region 分裂与 PD 负载均衡,跑一遍就能亲眼看到数据如何自动分片、如何被调度到空闲节点。
  1. class PD模拟调度中心,负责路由登记、分裂决策和迁移调度,对应真实 TiDB 中的 PD 组件。
  2. pd.route_to(key)根据 Key 范围找到对应 Region 和所在节点,模拟 PD 路由查找。
  3. r.put(key, "value_%d" % key)写入数据并判断 Region 是否超过容量上限,超限时返回 True 触发分裂。
  4. pd.split(r)把 Region 的 Key 从中间一分为二,一半数据留给旧 Region,一半归新 Region。
  5. pd.balance()比较各节点 Region 数量,把最忙节点的 Region 迁到最闲节点,模拟 PD 负载均衡。
  6. pd.assign(new_r)把新建 Region 放到当前 Region 最少的节点,模拟“新箱子放空仓库”的调度策略。

常见误区

误区TiDB 跟 ShardingSphere、MyCat 差不多吧?就是把 MySQL 拆成多库多表,应用层连一个代理,本质还是 MySQL。

正解分库分表中间件是“代理”,数据最终还是存在一堆 MySQL 实例里,事务、扩容、故障恢复都很难做好。TiDB 本身就是一个完整的分布式数据库:TiDB 节点负责计算,TiKV 负责存储,PD 负责调度,数据以 Region 为单位打散在多台机器上,自带分布式事务、自动分裂和故障自愈。它不是在 MySQL 外面套壳,而是从底层重新设计的数据库。

为何常见:TiDB 兼容 MySQL 协议,很多工具链和连接方式与 MySQL 一致,容易让人误以为它只是 MySQL 的一个转发层。

误区TiKV 存三份副本,那每次写入必须三个副本都写成功才能返回,不然数据会丢。

正解Raft 协议走的是多数派提交:三个副本只要两个确认(Leader + 一个 Follower),写入就能成功返回。第三副本慢一点、挂掉了都不影响写入。这既保证了数据不丢(多数派里总有最新数据),又避免了“等最慢节点”的延迟。代价是也只能容忍少数派故障——三副本最多挂一个,挂两个就写不了了。

为何常见:生活中“三份备份”通常意味着三份都写才算稳,人们习惯性地把“备份”和“全量确认”划等号。

误区PD 听起来像“数据库的大脑”,业务数据应该都存 PD 里,TiKV 只是缓存。

正解PD 只存元数据——Region 的位置、节点的心跳状态、调度策略、全局时间戳,不存任何业务数据。业务数据全部在 TiKV(行存)和 TiFlash(列存)里。类比起来,PD 是运营经理的台账本,台账只记录“箱子在哪里”,箱子本身在仓库里。元数据量很小但极其关键,PD 自己会组成一个小集群来保证可用性。

为何常见:大脑、中枢、调度中心这类词容易让人联想到“所有数据都汇聚于此”,忽略了它只管“地图”不管“货物”。

误区Region 太小会频繁分裂,那把默认 96MB 调成几个 GB,省事又省心。

正解Region 是数据调度和容灾的最小单位。Region 过大,一次分裂、迁移的数据量巨大,PD 调度时网络开销和影响面都会被放大;某节点宕机后,其他节点补副本也要传很久。保持适中的 Region(默认约 96MB)能让数据分布更细粒度地均衡,故障恢复更快,热点处理更灵活。

为何常见:人们容易类比“大箱子搬起来省事”,却忽略了数据库里“更小粒度 = 更灵活调度”的规律。

误区TiFlash 用列存,TiKV 用行存,那 TiFlash 是不是一个需要单独部署、单独同步数据的分析数据库?

正解TiFlash 是 TiDB 集群内的一个角色,不是外部系统。它作为 Raft Learner 自动从 TiKV 异步复制数据,同一个集群同时拥有行存和列存两种引擎,两边数据保持一致,用户不需要手动同步。查询优化器会自动判断:分析型大查询走 TiFlash,点查和写入走 TiKV,对外仍是一个数据库。

为何常见:传统架构里“行存库 + 分析库”是两套系统,需要 ETL 同步,这种惯性思维让人误以为 TiFlash 也是另一套独立数据库。

面试题

Q1. TiDB 架构中负责 SQL 解析与执行的是?

单选题 · easy

  • A.TiKV 存储节点
  • B.TiDB 计算节点← 正确答案
  • C.PD 调度节点
  • D.TiFlash 列存节点

答案:B。TiDB 节点相当于前厅的点单员和厨师,负责解析 SQL、生成执行计划并分发执行;TiKV 管存储,PD 管调度,TiFlash 管分析加速。选 B。

Q2. TiKV 中数据分片的基本单位是?

单选题 · easy

  • A.表(Table)
  • B.索引(Index)
  • C.分区(Partition)
  • D.区域(Region)← 正确答案

答案:D。TiKV 把数据按连续 Key 范围切成 Region,默认约 96MB,是复制、调度和容灾的最小单位。表和索引是逻辑概念,分区是 MySQL 的物理概念,都不是 TiKV 的分片单位。

Q3. 三副本 Raft 集群最多可容忍几个节点故障?

单选题 · easy

  • A.零个节点故障
  • B.一个节点故障← 正确答案
  • C.两个节点故障
  • D.三个节点故障

答案:B。Raft 多数派写入要求至少 F+1 个副本确认(三副本即 2 个)。三副本下最多容忍 1 个节点故障;挂掉 2 个,剩下 1 个凑不够多数派,就无法写入。公式 N=2F+1 中 F=1。

Q4. 下列哪项不是 PD 的职责?

单选题 · mid

  • A.维护路由元数据
  • B.分配全局时间戳
  • C.存储业务数据← 正确答案
  • D.调度 Region 副本

答案:C。PD 是运营经理,管台账(路由元数据)、盖章(全局时间戳)、调配(副本调度),但台账本不装货物——业务数据全部存在 TiKV 和 TiFlash 里。选 C。

Q5. 以下哪些属于 TiFlash 的特点?

多选题 · mid

  • A.采用列式存储格式← 正确答案
  • B.以 Raft Learner 身份同步← 正确答案
  • C.加速复杂分析查询← 正确答案
  • D.完全替代 TiKV 存储

答案:ABC。TiFlash 用列存服务分析查询,以 Raft Learner 身份异步复制 TiKV 数据,不参与投票所以不影响交易链路。它是 TiKV 的补充而非替代,写入主链路仍在 TiKV,所以 D 错。

Q6. 一个 Region 的默认数据量约为多少?

单选题 · mid

  • A.约为 64 千字节
  • B.约为 1 兆字节
  • C.约为 96 兆字节← 正确答案
  • D.约为 1 千兆字节

答案:C。默认 Region 约 96MB,类似仓库里统一规格的周转箱。这个大小让分裂、迁移、故障恢复的粒度适中。64KB 和 1MB 过小,1GB 过大,都不符合 TiDB 的默认设计。

Q7. TiFlash 如何从 TiKV 同步数据?

单选题 · hard

  • A.以 Raft Leader 身份同步
  • B.以 Raft Learner 身份同步← 正确答案
  • C.通过应用层双写直连
  • D.通过解析 binlog 同步

答案:B。TiFlash 作为 Raft Learner(学习副本)加入 Raft 组,被动接收日志并异步应用。它不参与投票,因此同步数据不会拖慢事务提交;也不需要应用双写或 binlog 解析,保持集群内数据天然一致。

Q8. TiDB 计算节点无状态设计的核心价值是什么?

单选题 · hard

  • A.不存数据因此可弹性扩缩容← 正确答案
  • B.不需要网络就能独立工作
  • C.单节点可处理海量数据
  • D.可完全替代存储节点

答案:A。无状态意味着 TiDB 节点不持久化业务数据,挂了一台,新的节点秒级顶上;并发高了,随时加节点分担请求。它仍需要网络访问 TiKV 和 PD,单机能力有限,也不可能替代存储层。

Q9. 以下属于 PD 调度能力的有?

多选题 · mid

  • A.感知节点心跳状态← 正确答案
  • B.自动迁移热点 Region← 正确答案
  • C.直接执行 SQL 语句
  • D.控制 Region 副本数量← 正确答案

答案:ABD。PD 通过心跳感知节点存活,通过负载统计迁移热点 Region,并控制每个 Region 的副本数量以维持容灾能力。执行 SQL 是 TiDB 计算层的职责,PD 不碰 SQL。

Q10. 关于 Region 分裂与合并的正确说法是?

多选题 · hard

  • A.数据超限会自动触发分裂← 正确答案
  • B.分裂让数据分布更细致← 正确答案
  • C.合并会降低查询性能
  • D.小 Region 合并减少元数据开销← 正确答案

答案:ABD。Region 超过容量会自动分裂成更小的 Region,让调度粒度更细;大量小 Region 会占用过多元数据内存与心跳流量,合并能降低开销、提升性能。因此 C 说“合降低性能”是反的,正确答案为 A、B、D。