1. 从一次数据库选型争论说起TiDB到底解决什么问题第一次接触TiDB是在一个数据量涨得比预期快得多的项目里。当时团队用的是一套经典的单机关系型数据库读写分离加缓存前期跑得挺顺。但业务量上来之后单表过亿、写入排队、DDL锁表、扩容只能垂直加配置几个问题叠在一起运维同学天天救火。讨论方案时有人提分库分表有人提换NoSQL也有人提了TiDB。那会儿我对TiDB的印象还停留在“国产数据库”“兼容MySQL”这种标签上直到真正搭了一套测试环境压了一遍才理解它想解决的核心矛盾是什么。TiDB是一个开源的分布式关系型数据库通常被归到NewSQL这个类别里。NewSQL这个词本身就是为了区分两件事传统关系型数据库比如单机MySQL、PostgreSQL能保证强一致和SQL能力但扩展性受限NoSQL比如各类KV、文档库扩展性好但牺牲了事务和复杂查询。TiDB的目标是把这两头的好处捏在一起——水平扩展能力 分布式事务 完整SQL支持 MySQL协议兼容。它解决的问题可以概括成一句话当你的数据量和并发量已经超出单机数据库的舒适区但你又不想放弃SQL、事务和关系模型时TiDB提供了一条不用改业务代码就能平滑扩展的路。适合谁来了解我觉得有三类人最该搞清楚它一是正在被分库分表折磨的后端和DBA二是做数据量较大的互联网业务、需要弹性扩缩容的架构同学三是想理解分布式数据库底层怎么运作的技术爱好者。哪怕你最后不用TiDB搞懂它的设计思路对理解分布式系统也很有帮助。下面我会从它的整体架构、核心组件、存储与计算分离的逻辑、事务模型、实际部署和踩坑经验几个角度把TiDB拆开讲清楚。内容基于我自己的实践和常见的社区实践补充涉及具体参数的地方会说明计算或选择理由。2. 拆开TiDB的骨架计算与存储分离到底分离了什么2.1 三个核心组件各司其职TiDB集群在逻辑上由三个角色组成理解这三个角色是理解一切的前提。TiDB Server无状态的SQL层。它负责接收客户端连接、解析SQL、生成执行计划、执行计算但不存数据。你可以把它理解成一个“智能路由计算引擎”。因为无状态所以可以随意加节点来提升并发处理能力前面挂个负载均衡就行。TiKV分布式事务型KV存储层。数据真正存在这里以Region为单位分片每个Region默认96MB可调。TiKV用Raft协议做多副本复制保证高可用和强一致。它对外暴露的是KV接口不是SQL接口。PDPlacement Driver集群的大脑。它负责存储元数据、调度Region的分布、分配全局单调递增的时间戳TSO。PD决定了数据怎么均衡地散落在各个TiKV节点上。这三者之间的关系用生活化的类比TiDB Server像餐厅的服务员和厨师负责接单和做菜TiKV像仓库负责存食材PD像仓库调度员决定哪个仓库放什么、什么时候调货。服务员不存食材仓库不管做菜调度员不碰食材也不做菜各干各的所以每一层都能独立扩展。2.2 为什么要把计算和存储分开这是TiDB最核心的设计决策也是它区别于传统单机数据库的根本。传统数据库里计算和存储耦合在一台机器上你要提升计算能力就得换更强的CPU要提升存储就得加更大的盘两者被绑死。TiDB把这两层拆开后带来几个直接好处。第一弹性扩展。读多写少的场景加TiDB Server节点就能提升查询吞吐数据量涨了加TiKV节点就能扩容存储和写入能力。两者互不干扰按需扩。第二故障隔离。SQL层某个节点挂了不影响存储层的数据存储层某个副本挂了Raft会自动选主业务几乎无感。第三资源利用率。计算层可以部署在CPU强的机器上存储层可以部署在磁盘大的机器上硬件选型更灵活成本更可控。但分离也有代价。计算层要拿数据必须通过网络从存储层拉这就引入了网络开销。TiDB的优化思路是计算下推——尽量把过滤、聚合等操作推到TiKV层做减少网络传输量。比如一个带WHERE条件的查询TiKV会先在本地过滤掉不匹配的行只把符合条件的数据返回给TiDB Server。这个设计对性能影响很大后面讲执行计划时会再展开。2.3 Region与Raft数据怎么做到既分散又可靠TiKV里的数据不是随便散的而是按Key的范围切成一个个Region。每个Region是一段连续的Key区间默认大小96MB。当某个Region超过阈值会自动分裂成两个当多个小Region可以合并时也会自动合并。这个机制让数据分布能随着写入动态调整。每个Region有多个副本通常默认3副本分布在不同的TiKV节点上。副本之间用Raft协议同步一个Leader负责读写其他Follower负责复制。Leader挂了Follower会在几秒内重新选主。Raft保证了强一致性——写入必须多数副本确认才算成功所以不会出现数据丢失或读到旧数据的情况。PD负责决定每个Region的副本放在哪些节点上目标是让数据均匀分布、负载均衡、故障域隔离。比如它会尽量让同一个Region的三个副本不在同一台机器、同一个机架甚至同一个可用区。这个调度逻辑是自动的运维基本不用手动干预但在节点数量少或者硬件差异大的时候可能需要调整调度策略。提示Region的大小不是越小越好。太小会导致Region数量爆炸PD和TiKV的元数据管理压力变大太大则分裂和调度的粒度变粗负载均衡不够细。默认96MB是社区实践里比较平衡的值除非有特殊场景不建议随意改。3. 事务与一致性TiDB怎么在分布式环境里保证ACID3.1 分布式事务的难点在哪单机数据库做事务相对简单因为所有数据都在一台机器上加锁、写日志、提交或回滚都在本地完成。但分布式环境下数据散在多个TiKV节点上一个事务可能同时修改多个Region的数据这就带来了几个难题怎么保证多个节点的修改要么全成功要么全失败怎么给事务分配一个全局一致的顺序怎么在不阻塞读的情况下保证隔离性TiDB的事务模型基于Google的Percolator论文核心思路是用两阶段提交2PC配合全局时间戳来实现分布式事务。具体来说PD提供一个全局单调递增的时间戳服务TSO每个事务在开始时拿一个start_ts提交时拿一个commit_ts。所有数据的多版本MVCC都按这两个时间戳来组织从而保证快照隔离。3.2 两阶段提交在TiDB里的具体流程一个写事务在TiDB里的提交流程大致如下客户端发起BEGINTiDB从PD获取start_ts。执行DML语句修改的数据先缓存在TiDB Server内存里同时写入TiKV的锁信息指向主键。客户端发起COMMITTiDB从PD获取commit_ts。进入两阶段提交先向所有涉及的TiKV节点发送prewrite请求写入数据但标记为未提交如果所有prewrite都成功再发送commit请求把数据标记为已提交。如果任何一步失败就回滚清理已写入的锁和数据。这个流程的关键在于commit_ts必须大于start_ts且全局唯一递增。这样任何读操作拿到的快照要么看到事务提交前的数据要么看到提交后的数据不会看到中间状态。这就是快照隔离的实现基础。3.3 读写冲突与锁清理分布式事务里最麻烦的是冲突处理。假设事务A修改了Key1还没提交事务B也要修改Key1这时候B会碰到A留下的锁。TiDB的处理方式是B会等待一段时间如果A一直不提交B会尝试清理过期锁通过检查锁的TTL。如果A实际上已经挂了B可以强制回滚A的事务然后继续自己的操作。读操作遇到锁时默认行为是等待锁释放但可以通过设置tidb_snapshot或者使用历史读来绕过。这个机制在高并发写入场景下需要特别注意因为锁冲突会导致延迟上升。实际调优时可以通过减少大事务、拆分批量写入、调整锁等待超时参数来缓解。注意TiDB默认的事务隔离级别是快照隔离SI不是可串行化。这意味着在某些极端并发场景下可能出现写偏斜write skew问题。如果业务对串行化有强需求需要显式使用SELECT ... FOR UPDATE或者调整隔离级别但这会带来性能下降。大多数互联网业务用快照隔离就够了但金融类场景要仔细评估。3.4 MVCC与GC多版本数据的清理TiDB用MVCC保存数据的多个版本每个版本带一个时间戳。读操作根据start_ts找到对应版本的数据。这带来一个必然问题旧版本数据会越积越多需要定期清理。TiDB的GC机制由PD协调默认每10分钟触发一次清理安全点之前的所有旧版本。安全点的计算基于当前活跃事务的最小start_ts保证不会清理掉还在用的事务需要的数据。GC参数gc_life_time默认是10分钟意味着旧版本至少保留10分钟。如果业务有长事务或者长时间的历史读需求需要调大这个值。但调太大会导致存储空间膨胀需要权衡。我见过有团队因为长事务没控制好GC安全点推不上去导致TiKV磁盘暴涨最后只能手动kill长事务来恢复。4. 从部署到跑通一套最小可用集群的搭建思路4.1 环境准备与拓扑规划搭建TiDB测试环境最省事的方式是用TiUP。TiUP是官方提供的包管理工具一条命令就能拉起一套本地集群。但生产环境不能这么随意需要先规划拓扑。最小生产可用拓扑通常是3个TiKV节点保证3副本、3个PD节点保证调度高可用、2个以上TiDB Server节点保证SQL层高可用。如果机器资源有限PD可以和TiKV混部但不建议TiDB Server和TiKV混部因为两者资源竞争会影响性能。硬件方面TiKV对磁盘IO敏感建议用SSD最好是NVMe。TiDB Server对CPU和内存敏感因为要做SQL解析和计算。PD对资源要求不高但需要低延迟的网络。网络延迟对Raft复制影响很大跨机房部署时要把PD的调度策略调成按机架感知避免跨机房同步导致写入延迟飙升。4.2 用TiUP部署集群的关键步骤假设你已经准备好了机器并且配置好了SSH互信部署流程大致如下# 安装TiUP curl --proto https --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh # 声明环境变量 source ~/.bashrc # 生成拓扑模板 tiup cluster template topology.yaml # 编辑topology.yaml填入机器IP、端口、数据目录等信息 # 然后执行部署 tiup cluster deploy my-cluster v7.5.0 topology.yaml --user root -p # 启动集群 tiup cluster start my-cluster # 查看集群状态 tiup cluster display my-cluster拓扑文件里几个关键配置需要留意deploy_dir和data_dir要指向大容量磁盘tikv_servers的config里可以设置raftstore.region-size等参数pd_servers的config里可以设置调度策略。部署完成后用MySQL客户端连接TiDB Server的4000端口就能像用MySQL一样操作。4.3 验证集群是否正常部署完别急着上业务先做几项基本验证。第一用tiup cluster display确认所有节点状态是Up。第二连上TiDB执行SELECT * FROM information_schema.cluster_info确认各组件版本和状态。第三建一张测试表写入一批数据然后手动kill一个TiKV节点观察写入是否自动切换到其他副本恢复后数据是否一致。第四用sysbench跑一轮读写压测看QPS、延迟、Region分布是否均衡。这些验证步骤看起来繁琐但能提前暴露很多问题。我遇到过拓扑文件里端口写错导致PD连不上TiKV的情况也遇到过磁盘挂载点没配好导致数据写到系统盘的情况。提前验证比上线后救火划算得多。5. 实际使用中绕不开的几个坑与调优经验5.1 热点问题自增主键带来的写入瓶颈TiDB里最经典的坑就是自增主键导致写入热点。因为自增ID是连续递增的新写入的数据总是落在最后一个Region上导致这个Region所在的TiKV节点成为瓶颈其他节点闲着。这个问题在单机数据库里不明显但在分布式环境里会被放大。解决方案有几种。一是用AUTO_RANDOM代替AUTO_INCREMENT让TiDB自动打散主键写入就能均匀分布到多个Region。二是用业务侧生成的散列主键比如雪花算法ID。三是用SHARD_ROW_ID_BITS配合PRE_SPLIT_REGIONS在建表时预分裂Region。这几种方案各有适用场景AUTO_RANDOM最省事但主键不再连续如果业务依赖连续ID就要慎重。提示热点问题不只在写入时出现某些查询模式也会导致读热点。比如按时间范围查最新数据如果数据按时间顺序排列读请求也会集中在一个Region。解决办法类似要么打散数据分布要么用TiFlash做列存副本分担读压力。5.2 执行计划不走索引统计信息与下推的配合TiDB的优化器基于代价选择执行计划依赖统计信息。如果统计信息过期优化器可能选错索引导致全表扫描。定期执行ANALYZE TABLE更新统计信息是基本操作但要注意ANALYZE本身也消耗资源大表上要避开业务高峰。另一个常见问题是计算下推没生效。比如一个带函数的WHERE条件如果函数不是TiKV支持的下推函数就会把数据拉到TiDB Server再过滤网络传输量巨大。解决办法是尽量用简单的比较条件或者用生成列把函数结果存下来。执行计划里看到Selection算子出现在TableReader之上而不是之下就说明下推没生效需要检查SQL写法。5.3 大事务与批量写入的取舍TiDB对事务大小有限制默认单事务不超过100MBtxn-total-size-limit。批量导入大量数据时如果放在一个事务里很容易超限报错。正确做法是拆成小批次每批几千到几万行用INSERT INTO ... VALUES批量插入或者用LOAD DATA。但批次太小又会导致事务开销占比高写入吞吐上不去。实践中每批5000到10000行是比较平衡的值具体要看单行大小和集群负载。另外大事务还会阻塞GC导致旧版本数据堆积。如果业务确实需要大事务要同步调大gc_life_time并监控TiKV的磁盘使用率。我见过一个案例某团队用单事务导入千万行数据结果GC安全点卡住TiKV磁盘一周内涨到90%最后只能停业务手动清理。5.4 监控与告警哪些指标最该盯TiDB自带Grafana监控面板指标很多但日常最该盯的是这几个TiKV的raftstore相关指标看Raft复制是否健康、scheduler相关指标看PD调度是否正常、TiDB的connection count和duration看SQL层负载、以及region health看是否有Region异常。告警阈值要根据业务基线来定不能照搬默认值。我个人的经验是写入延迟突然上升时先看TiKV的apply wait duration和raft store CPU这两个指标能快速定位是Raft复制慢还是磁盘IO瓶颈。读延迟上升时先看TiDB的cop task和executor指标判断是下推没生效还是TiKV扫描量大。这些排查路径比盲目看大盘效率高得多。6. 什么时候该用TiDB什么时候不该用6.1 适合TiDB的典型场景TiDB最适合的场景有几个共同特征数据量大到单机扛不住、业务需要水平扩展、又不想放弃SQL和事务。比如互联网业务的订单库、用户行为库、实时报表库这些场景写入量大、查询模式多样、对一致性有要求。另外TiDB的HTAP能力通过TiFlash做列存副本让它在实时分析场景也有用武之地一套系统同时扛OLTP和OLAP省去了数据同步的麻烦。还有一个容易被忽略的场景是数据库 consolidation。有些团队维护着几十套MySQL分片运维成本极高。迁到TiDB后分片逻辑由TiDB内部处理业务层不用再关心路由运维复杂度大幅下降。这个价值在中小团队里尤其明显因为省下来的人力可以投到业务上。6.2 不适合TiDB的情况TiDB不是万能的。如果数据量不大比如单表千万级以下单机MySQL完全够用上TiDB反而增加运维复杂度和硬件成本。如果业务对延迟极度敏感比如要求P99在1ms以内TiDB的网络开销和Raft复制延迟可能达不到要求这时候要考虑单机数据库或者内存数据库。如果业务不需要事务和SQL纯KV场景用Redis或HBase可能更合适。另外TiDB的硬件成本不低。三副本意味着存储成本是单机的三倍加上PD和TiDB Server节点整体投入比单机方案高不少。所以选型时要算清楚账业务增长带来的扩展需求是否值得这个成本。如果业务增长不确定可以先从小集群起步验证后再扩。6.3 从单机迁移到TiDB的注意事项迁移是个大工程不是换个连接串就完事。首先要评估兼容性TiDB虽然兼容MySQL协议但有些MySQL特性不支持或者行为有差异比如存储过程、触发器、某些函数。迁移前要用工具做全量SQL审核把不兼容的语句找出来改写。其次要规划数据迁移方案。小数据量可以用Dumpling导出、Lightning导入大数据量要用DMData Migration工具做增量同步先全量后增量最后切流量。切流量前一定要做双向校验确保两边数据一致。我见过迁移后才发现某些字段精度丢失的案例就是因为没做校验。最后是回滚方案。迁移不是一次性的要有回滚路径。切流量后观察一段时间确认没问题再下线旧库。如果发现问题能快速切回去。这个准备看起来多余但真出问题时能救命。7. 我对TiDB的一点个人理解用了一段时间TiDB后我最大的感受是它把分布式系统的复杂性封装得很好。作为使用者你不需要理解Raft怎么选主、PD怎么调度、2PC怎么提交只需要像用MySQL一样写SQL就行。这种“把复杂留给自己把简单留给用户”的设计哲学是它能被广泛接受的关键。但封装得好不代表可以完全不懂原理。恰恰相反遇到性能问题、热点问题、事务冲突时理解底层机制才能快速定位。比如知道Region分裂逻辑就能理解为什么自增主键会热点知道Raft复制流程就能理解为什么跨机房部署延迟高。这些知识不是运维专属后端开发也该了解。最后分享一个实用建议如果团队准备上TiDB先搭一套测试环境用真实业务SQL跑一遍重点观察执行计划、热点分布、事务冲突率。这些数据比任何文档都有说服力。测试环境踩的坑越多生产环境越稳。数据库选型没有银弹只有适不适合搞清楚自己的需求再决定要不要用TiDB。
阅读完成 · 觉得有帮助?