前段时间帮一家做金融结算系统的客户做POC,业务方把一套跑在MySQL上的账务模块直接丢过来,丢下一句"TDSQL不是兼容MySQL吗?启动起来直接跑就行"。结果前三天确实跑得很顺,JDBC连上、建库建表、CRUD全通,团队里已经有人下结论说"这就是个MySQL换壳"。等到开始做压测、跑故障演练、看监控面板的时候,问题一个个冒出来——不是不能用,而是"能用"和"用得好"之间,隔着对分布式架构的理解鸿沟。
那次之后我把整个评估过程重新整理了一遍,从兼容性、运维方式、性能表现三个维度逐层拆解。如果你是 DBA、架构师或者正在做数据库选型的技术负责人,这篇评估思路可以帮你少走不少弯路。
1. 一场压测引发的评估:我为什么盯上TDSQL
1.1 评估的缘起与三张评估表
客户当时的情况很典型:核心业务跑在MySQL上,数据量到了单库瓶颈,读写延迟开始抖动。他们看了一圈分布式数据库,TDSQL进入候选名单的理由无非三个:腾讯系产品线有金融场景背书、对外宣传兼容MySQL、支持分布式事务。
但我做选型有个习惯,先把"评估"这件事拆成一张表,避免被厂商的宣讲PPT带着走。当时的评估维度分三层:
| 评估维度 | 核心问题 | 评估方法 |
|---|---|---|
| 兼容性 | 现有业务代码改动能压到多小 | 直接用真实业务SQL和代码迁移试跑 |
| 运维性 | 日常DBA工作能否沿用MySQL经验 | 独立部署一整套实例,模拟扩缩容/备份/故障 |
| 性能 | 分布式带来的收益和代价分别在哪 | Sysbench+TPC-C压测,分场景记录 |
后面所有内容都是围绕这三张表展开的。先说结论:TDSQL确实兼容MySQL,但这个"兼容"是有边界的,边界在哪、边界之外怎么处理,才是评估真正有价值的部分。
1.2 我给评估设的边界条件
写这篇之前先交代清楚测试环境,避免大家照着做的时候因为版本差异踩坑。我用的TDSQL分布式版,版本是当时线上的稳定版本(5.7内核兼容模式),集群规模是3个数据节点、2个网关节点、2个调度节点,另外单独部署了赤兔管理平台和扁鹊监控系统。整体拓扑是标准的"计算层-网关层-存储层"三层架构。
业务场景用的是客户真实场景的简化版:一张用户表、一张订单表、一张订单明细表,共3100万行订单数据,撑在10个分片上。所有压测和生产模拟都基于这套数据,因为单纯用sysbench跑个select 1,根本暴露不出分布式数据库的真实问题。
2. 兼容性评估:从JDBC到存储过程,逐层过筛
2.1 协议与驱动:连接层几乎没有障碍
先说最基础的连接层。TDSQL的接入节点对外提供标准MySQL协议,所以Java系的Connector/J、Go的go-sql-driver、Python的PyMySQL、Node.js的mysql2全部可以直接连,不需要换驱动。这一点当时节省了大量改造时间。
不过有两个细节值得注意:
第一,连接串里面不需要写特殊的分布式参数,但如果你用老版本的JDBC驱动,需要确认SSL握手的兼容性。我们当时用的Connector/J 5.1.x连TDSQL的网关节点,偶发连接超时,后来对比排查发现是网关节点默认开启了SSL校验,老驱动握手慢导致的。解决办法要么升级驱动到8.0.x,要么在连接串里显式关掉SSL。这不是TDSQL独有,很多MySQL生态的中间件都有类似问题。
第二,连接池参数要重新调。TDSQL的网关节点本身会做连接聚合,应用侧连的是proxy而不是后端数据节点,所以应用连接池的maxActive可以比连MySQL时调大一些,因为后端实际连接数会被proxy收敛。当时把Druid连接池的maxActive从50调到200,后端数据节点的线程压力并没有明显上升,这就是proxy连接复用的效果。
2.2 SQL语义差异:窗口函数、排序规则与自增字段
连接层没问题,SQL语义层才是兼容性的重头戏。
先说好消息:TDSQL对常见SQL语法支持得比较完整,包括多表JOIN、子查询、UNION、聚合函数、窗口函数(MySQL 8.0模式的窗口函数语法可以跑)、大部分DDL和DML语句,存储过程和触发器也能用。我们迁移第一轮,业务侧600多条SQL只有不到30条需要改动,比例大概5%。
但这30条SQL的坑值得展开说:
第一个坑是分片表的JOIN限制。如果两张都是分片表,JOIN的条件里必须带上两个表的分片键等值条件,否则网关节点会把SQL下推到所有分片做全表扫描,然后在网关层做聚合。功能上能跑通,但性能会崩。我们有一条查询用户最近订单的SQL,users和orders表按user_id分片,JOIN条件只写了u.id = o.user_id,这时候网关判断条件是等值分片键关联,性能正常;但如果写的是WHERE o.create_time > xxx这种不带分片键的过滤,就会触发全分片扫描,一个本来20ms的查询直接变成900ms。
第二个坑是自增字段。单机MySQL里自增主键是连续的,TDSQL分片模式下自增保证的是全局唯一,不保证连续。TDSQL内部是用自增序列实现的,相当于一个全局发号器。业务方如果写了INSERT ... SELECT LAST_INSERT_ID()这种代码,短连接场景下能拿到本次会话的ID,但如果是连接池复用连接且没有清掉上一个会话状态,可能拿到旧值。实际改造时统一改成了先插入再按业务订单号反查ID。
第三个坑是排序规则和隐式转换。TDSQL内核走的还是MySQL那一套,但分片键字段的数据类型如果和SQL里的参数类型不一致,隐式转换可能导致网关路由计算错误。我们遇到过varchar分片键传入数字参数,网关按哈希计算后路由到错误分片,返回空结果集,排查了很久。后来规范要求:所有分片键的查询参数强制显式转成字符串。
另外补一个冷门点:GROUP_CONCAT函数在分片模式下返回的结果顺序不保证,因为不同分片返回的partial结果在网关层合并时,顺序取决于各分片返回的先后。业务上如果依赖这个函数的默认排序,需要显式加ORDER BY。
2.3 周边生态兼容:ORM、中间件与BI工具实测
现在很多业务系统用ORM框架,这块也得过一遍。我们实测了MyBatis-Plus、Spring Data JPA、Hibernate三种,只要不走分片键查询的查询条件都能直接跑通,框架生成的SQL基本兼容。但注意一点:MyBatis-Plus的Page分页插件在TDSQL上会生成LIMIT offset, size,当offset很大的时候(比如超过100万),网关层的聚合排序和limit下推逻辑会让性能下降明显。这种深分页场景建议改成基于分片键的游标分页。
中间件层面有个反直觉的事:不要给TDSQL再套一层ShardingSphere。我们刚开始觉得加ShardingSphere可以做读写分离和数据脱敏,结果两层分片逻辑叠在一起,网关层路由和ShardingSphere的路由互相冲突,数据分布完全乱掉。正确做法是让TDSQL网关承担分片路由,业务侧只要保持单库连接方式就好。TDSQL的设计哲学就是"对业务隐藏分片",中间件层越薄越好。
BI工具方面,FineReport和帆软连接TDSQL走的也是MySQL协议,连接和元数据读取正常,但大量BI报表的SQL往往是复杂的多表JOIN,不带分片键过滤条件的会触发全分片扫描。针对BI查询,建议单独建一个汇聚库,通过TDSQL的数据同步能力把各分片数据汇集到单表,BI只查汇聚库,避免查询压力打散到所有数据节点。
3. 运维视角:把TDSQL当MySQL管,第一天就吃大亏
3.1 节点拓扑与监控维度的变化
传统MySQL DBA上手TDSQL的第一道坎是拓扑认知。以前管一主两从,看主从延迟、看半同步复制状态、看binlog位点,这套经验在TDSQL上不够用了。
TDSQL分布式版的节点分三类:网关节点(接入和路由)、调度节点(元数据管理和任务调度)、数据节点(实际存储计算)。数据节点内部还有强同步复制机制,一组数据节点里通常包含一主一备,主备之间通过强同步协议保证事务不丢失。所以监控维度从"一个实例"变成了"整个集群":
| 监控对象 | MySQL传统指标 | TDSQL需要额外关注的指标 |
|---|---|---|
| 网关层 | 无对应 | proxy连接数、请求队列深度、分片路由耗时 |
| 调度层 | 无对应 | 节点心跳、元数据一致性、任务调度延迟 |
| 数据节点 | CPU/内存/磁盘/慢查询 | 分片数据分布均匀度、主备强同步状态、binlog堆积 |
| 集群整体 | 无对应 | 集群容量水位、重分布任务进度、跨分片事务比例 |
刚开始我把MySQL那套告警规则直接搬过来,结果第一天就漏了一个关键问题:某个分片的磁盘使用率到了85%,而其他分片只有40%。单看每个节点的磁盘监控,85%还没触达90%的告警阈值,但这是数据倾斜的征兆。等到建了倾斜告警才发现,那个分片上的数据量已经是其他分片的两倍。这种事在MySQL单实例上不存在,在分布式架构里必须当作一等一的故障隐患来看。
3.2 备份恢复、扩容与版本升级的实际操作
运维的高频操作是备份、扩容、升级,这三项TDSQL都有配套方案,但注意事项和MySQL差异很大。
备份恢复。TDSQL支持物理备份和逻辑备份,可以基于备份文件做时间点恢复。实际运维时我建议启用到备份文件所在的独立存储,不要放在数据节点本地磁盘,否则磁盘故障时备份和数据一起丢。恢复的流程和MySQL单体物理备份恢复有点类似,但需要先在赤兔管理平台上注册一个空的集群,再把备份文件分发到对应数据节点。整个流程走下来大概40分钟(数据量1TB规模),这和MySQL的恢复时间差不多,但步骤多了一个"集群注册"环节。
在线扩容。这是分布式数据库的核心卖点。TDSQL支持在线增加数据节点,扩容过程会自动触发数据重分布。但要注意,重分布期间IO压力明显上升,如果业务高峰期扩容,会让延迟翻倍。我们的操作经验是把扩容窗口安排到凌晨,并且一次只加一个节点,观察重分布速率正常后再加下一个。重分布速率受限于网络带宽和磁盘IO,1TB数据迁到新节点大概需要1到2个小时,期间旧节点数据只读不阻断业务。
版本升级。TDSQL升级是滚动式的,数据节点逐个升级,每个节点升级时会把该分片的主备切换一次。这个切换过程业务无感知,但如果你在升级期间刚好有分布式事务在跑,事务会被回滚。稳妥做法是升级前先做一轮连接数高峰检测,并且给每个业务账号设置一个"维护窗口"级别的只读权限切换开关。
3.3 巡检清单:我最常看到的几类隐患
几个月运维下来,我总结了一份分布式巡检清单,不一定全面,但都是亲眼见过的坑:
- 分片倾斜检查。每个分片的表行数、磁盘占用、活跃连接数三个指标拉出来对比,偏差超过20%就要查分片键设计。
- 网关节点资源。proxy的内存使用量会随着连接数增加而上升,如果出现频繁Full GC,往往是有业务连接泄漏。可以在网关节点的监控页面看活跃连接数和内存曲线是否同步增长。
- 跨分片事务比例。如果监控里跨分片事务占比超过30%,性能衰减会比较明显,优先排查业务SQL是否漏写了分片键。
- binlog和日志空间。数据节点和网关节点的日志默认保留天数可能不一样,磁盘小的时候,网关节点的访问日志最容易先把磁盘撑满。
- 强同步状态。强同步模式下,如果备节点临时故障,主节点的写入性能会受到影响。建议给强同步状态单独配一个告警通道,别和普通告警混在一起,这个状态变化非常值得留意。
4. 性能测试的三种典型场景与结果分析
4.1 压测方法与工具准备
性能部分我是用Sysbench和TPC-C混合来测的。Sysbench主要测单表简单查询和只写场景,TPC-C模拟的是订单+库存+支付这类复杂事务模型。
压测环境是3个数据节点,10个分片,每分片300万行订单数据。机器配置统一是16核32G SSD,网络万兆。测试前我把数据预热了30分钟,确保InnoDB缓冲池里的数据页是热的,避免冷数据读放大干扰结果。
压测有几个前置条件容易被忽略,先列一下:
- 关掉查询结果集的网络压缩。网关层默认开压缩的话,CPU会有额外消耗,压测数据不代表真实水平。
- 确认binlog刷盘策略。TDSQL强同步模式下,binlog刷盘策略对TPS影响很大,建议测试时用生产同款配置,不要用默认值。
- 分片键要均匀。我用user_id做分片键,测试数据先生成好再灌库,确保每个分片的数据量和访问热度接近。
4.2 读多写少场景:网关路由的代价
先测的是读多写少场景,比例大概是95%读、5%写,用Sysbench的oltp_read_write模式跑。
结果比较有意思:单分片键点查(SELECT * FROM orders WHERE user_id = ?)的P99延迟是1.8ms左右,对比客户原来的MySQL单机1.2ms,网关层多了一次路由计算和网络转发,大概增加了0.6ms。这个代价在点查场景下可以接受。
但非分片键查询就是两个世界了。SELECT * FROM orders WHERE order_no = ?这种查询,order_no不是分片键,网关只能把请求广播到所有10个分片,然后等所有分片返回结果再做汇总。实测P99延迟到了38ms,是分片键查询的21倍。数据量越大、分片越多,这种全分片扫描的放大效应越严重。
所以读多写少场景的结论是:分片键命中与否,决定了TDSQL和MySQL的性能差距方向。业务能保证每条查询都带分片键,性能接近甚至超过单机MySQL;一旦出现大量非分片键查询,性能会随分片数量线性恶化。
4.3 写入与分布式事务场景:2PC的现实开销
写场景我用TPC-C的新订单事务来压,这个事务涉及订单表、订单明细表、库存表三张分片表的写入,天然是分布式事务。
TDSQL的分布式事务基于两阶段提交(2PC),配合内部协调者保证一致性。实测下来:
- 全部数据落在同一个分片的事务(同分片事务),TPS约8200。
- 跨分片事务(三张表分布在两个以上分片),TPS掉到约2600,降幅68%。
这就是分布式事务的现实代价。每笔跨分片事务要多一倍的prepare/commit往返,协调者还要写事务日志,延迟和吞吐都会受影响。TPC-C标准的完整事务里,约30%的订单跨分片,整体TPS大约在3800左右,比同分片事务差不少。
这并不是TDSQL的问题,任何分布式数据库处理跨分片强一致事务都有这个代价。关键在于设计上尽量让事务内所有的写操作落在同一个分片。举个例子,把订单表、订单明细表都按user_id分片,那么"一个用户下订单"这件事的所有写入都落到同一个分片上,事务退化为单分片事务,TPS可以拉回到7000以上。只有"查询所有分片的汇总报表"这类分析型事务才不得不跨分片。
批量写入场景也测了一轮。原来MySQL习惯用INSERT INTO ... VALUES (...), (...)...批量插,TDSQL同样支持,而且效果明显。我们测了100条一批和单条插入对比,批量模式总耗时只有单条的1/7,业务方改造的时候强烈建议把写入接口改成批量模式,收益立竿见影。
4.4 慢查询分析与参数调优实测
压测过程中暴露出几条慢SQL,正好拿来做调优案例。
第一条是统计类查询:SELECT user_id, COUNT(*) FROM orders WHERE create_time > ? GROUP BY user_id,这条SQL不带分片键,所有分片都要把符合条件的行全部扫描一遍,再回网关层做GROUP BY。压测时这条SQL跑一次要4.2秒。优化方案有两个方向,要么改成定时预计算结果,写入一张按天汇总的分片表,查询只扫汇总表;要么在TDSQL的监控页面开启"全分片扫描SQL明细"功能,把这类SQL找出来做应用层改造。预计算方案上线后查询时间降到了80ms。
参数调优方面,我实际调整过三个参数:
innodb_buffer_pool_size:数据节点默认只分配了服务器物理内存的60%,我们的机器是32G,调到20G之后读写性能提升约15%。这个参数和MySQL完全一样。- 网关层连接队列长度:压测中发现proxy的请求排队数超过200时,P99延迟会明显拉高。把队列长度调大并配合应用端连接池上限调整后,排队数稳定在50以下。
- 事务日志刷盘策略:强同步模式下为了数据安全,默认每次事务提交都要同步刷盘。如果业务可以容忍秒级故障窗口,可以调整为组提交模式,TPS能提升30%左右。但这个取舍必须由业务方确认,DBA不能自己拍板。
5. 分片键:分布式数据库性能与可用性的总开关
5.1 分片键选错后的典型症状
写到现在,不管是兼容性、运维还是性能,所有的坑最后都指向同一个根源——分片键。这也是整个评估过程中我们团队最痛的领悟。
分片键选错的典型症状有三个:
第一,数据倾斜。某个分片的磁盘使用率和活跃连接数比其他分片高出一大截,热点全打在一个节点上。我们测试时一开始用订单日期作为分片键,结果近三个月的数据全集中在两个分片里,这两个分片的IO直接打满,其他分片空闲。这个属于典型的按时间序列分片反模式。
第二,非分片键查询比例飙升。分片键如果是order_no,业务员查"某个客户的所有订单"就尴尬了,因为查询条件不带order_no,网关只能全分片广播,客户越大的查询越慢。
第三,分布式事务比例居高不下。分片键设计没有考虑业务的事务边界,导致每个业务操作都要跨分片协调。
5.2 三种分片方式与广播表的使用场景
TDSQL支持三种分片方式,按实际使用频率排序:
- 哈希(HASH)分片:根据分片键的哈希值取模路由到分片。适合分片键取值分布均匀、查询以等值条件为主的场景。我们订单表默认就是这种。
- 范围(RANGE)分片:按分片键的值范围划分区间,适合按时间范围做数据生命周期管理的场景,比如日志表。但要注意,范围分片天然容易产生写入热点,最新时间段的数据会全部落在同一个分片里。
- 列表(LIST)分片:按分片键的枚举值列表路由,适合省份、机构这类固定枚举。
除了分片表,TDSQL还有一个广播表(小表广播)的概念。把配置表、参数表这类数据量小、几乎不更新、高频关联查询的表设置为广播表,每个分片都会存储一份完整副本,查询时不需要跨分片JOIN。我们当时把客户等级表、产品配置表设成了广播表,JOIN性能提升非常明显。注意广播表不适合频繁更新的表,因为每次更新都要同步给所有分片,写放大倍数等于分片数量。
5.3 一次失败的改分片键经历
讲个反面案例。产品经理在测试阶段提出,原定的user_id分片键不能满足"管理员后台按手机号模糊查用户"的需求,要求把分片键改成手机号。
我们查了一圈,TDSQL不支持在线修改分片键。想改分片键,只能新建一个按手机号分片的新表,把旧数据全量迁移过去,业务代码里的查询条件全部改成先按手机号路由。旧表的数据还要保留一段时间做双写兼容。整个改造评估下来,DBA工作量2人天,业务改造工作量5人天,数据全量迁移和校验3天,而且迁移期间不能做在线DDL变更。
后来我们说服产品经理采用了一个折中方案:保留user_id分片,新建一张手机号到user_id的映射表(用手机号分片),管理员后台先查映射表再查订单表。这样既满足了需求,又不用动核心表的分片键。
这个案例的教训是:分片键必须在建表之前设计好,而且设计依据不是"哪个字段最常用",而是"这个表的所有重要查询条件里,哪个字段能最大化把查询和事务收敛到单分片"。通常联合主键里的业务标识字段比随机生成的sequence更适合当分片键,因为业务上天然按它聚集数据。
6. 回到现实:TDSQL适合谁,迁移前要想清楚什么
6.1 哪些场景我建议用TDSQL
经过这轮评估,我对TDSQL的适用场景有了比较清晰的边界感。
适合用TDSQL的场景有这么几类:
- 高并发在线事务处理(OLTP):业务量达到单机MySQL瓶颈,需要水平扩展,且查询能够按分片键收敛。典型如订单中心、支付流水、账户台账、积分系统。
- 强一致事务要求高:金融、交易类场景下数据不能丢、不能错,TDSQL的强同步复制和分布式事务能力在这个维度上是加分项。
- 已有MySQL技术栈:团队全是MySQL DBA,不想引入一套全新的运维知识体系,TDSQL的学习曲线相对平缓。
不建议用的场景也有:
- 报表和分析型业务:大量跨分片聚合查询,在分布式架构下反而是负担,建议用分析型数据库。
- 数据量不大、但关系复杂:几十张表强关联,分片键很难设计得让所有JOIN都收敛,这种情况单机MySQL或者传统主从架构反而更简单高效。
- 超高并发写入但无需强一致:如果需要的是最终一致性加极致写入吞吐,NoSQL或类NewSQL可能是更合适的路线。
6.2 迁移改造的隐形成本
很多决策者只盯着"兼容MySQL"四个字,以为迁移成本约等于零。实际上迁移工作至少包含这几个隐藏项:
- 分片键改造:存量表如果本身没有合适的分片键字段,或者唯一键不包含分片键,就得加字段、改写入逻辑。这是最大头的成本。
- SQL代码整改:非分片键查询、跨分片JOIN、自增字段依赖这类问题,需要逐条Review和改写。我们当时600条SQL改了30条,但改这30条花了整个项目周期四分之一的时间。
- 数据迁移工具链:TDSQL提供了数据迁移工具,但从MySQL迁移到TDSQL前,建议先做一次全量数据校验脚本,避免源库和目标库之间字符集、排序规则不一致导致的数据差异。
- 运维流程重建:备份恢复演练、扩容演练、故障切换流程都要重新设计和验证。这部分在项目计划里经常被压缩,但恰恰是上线后最要命的环节。
6.3 一张评估检查清单
按我这轮评估的完整流程,整理了一份清单,可以直接拿去用作选型参考:
- [ ] 列出业务核心表的所有查询场景,标注每个查询是否带分片键等值条件
- [ ] 检查所有唯一索引和主键,确认是否包含分片键
- [ ] 统计跨分片事务比例,超过30%要重新评估分片键设计
- [ ] 验证JDBC/ORM/BI工具的连接兼容性
- [ ] 跑一遍真实业务SQL集,统计需改写的SQL比例和复杂度
- [ ] 做一次备份恢复演练,记录RTO和RPO是否符合预期
- [ ] 在线扩容演练,确认扩容期间业务延迟可接受
- [ ] 压测同时覆盖分片键查询和非分片键查询,别只测乐观场景
- [ ] 检查数据倾斜监控和告警是否完善
- [ ] 确认版本升级方案和回滚方案可执行
这份清单不是TDSQL独有的,任何分布式数据库选型都适用。核心逻辑就一条:别在POC阶段只看厂商演示的顺畅场景,把生产环境最难啃的存量SQL、最恶劣的查询模式、最高频的运维动作全部提前试一遍。
我后来再参与分布式数据库选型,习惯把分片键设计评审放在所有工作的第一步,而不是等压测发现问题再回头改。数据库一旦分片,架构上的取舍就基本定死了,后面的兼容、运维、性能都是在为这个选择买单。TDSQL整体给我的印象是工程成熟度不错,兼容性在同类产品里算很能打的,但它再好用,也解决不了分片键设计不合理带来的架构问题。评估工具的过程,本质上是在评估自己团队的架构设计能力。