从分库分表到 NewSQL:TiDB/OceanBase替换ShardingSphere的实战路径

150 阅读15分钟

一、一个电商项目的真实困境

2020 年,笔者参与架构评审的某中型电商平台(日活约 200 万,SKU 超 800 万,日均订单量约 50 万单),其核心交易库采用 MySQL + ShardingSphere 的分库分表方案:订单表按用户ID取模分为 256 张表,分布在 16 个 MySQL实例上;商品库存表按商品ID分为128张表,分布在8个实例上。

这套架构在 2020-2022 年运行良好。但到了 2023 年大促期间,问题集中爆发:

  • 运营后台需要查询"某品牌近 30 天全渠道销售汇总",涉及跨 256 张分表的聚合查询,ShardingSphere 需要在中间件层做结果归并,单次查询耗时超过 40 秒,频繁超时。
  • 大促前扩容,需要将 256 张表扩展为 512 张,涉及全量数据 Re-sharding,团队提前两周做演练,迁移窗口期仍需 4 小时,期间核心交易降级为只读。
  • 一次 DDL 变更(订单表加一个字段),需要在 256 张表上逐一执行,耗时 3 小时,期间部分分片出现锁等待。
  • 跨库事务(下单扣库存 + 创建订单 + 扣减优惠券)依赖 Seata 的 AT 模式,大促高峰期事务冲突率飙升,超时率从日常的 0.1% 上升到 2.3%。

DBA 团队 3 人,超过 60% 的工作时间花在"维护分片规则、处理扩容、排查跨片慢查询"上。架构组在 2024 年初正式立项:用 NewSQL 替换分库分表架构。

二、什么是分库分表?

2.1 基本概念

分库分表是一种在应用层或中间件层实现的数据水平/垂直拆分策略,其核心思想是:将一张大表或一个大库,按照某种规则拆分为多个小表或多个小库,每个小表/小库仍然是一个独立的单机数据库实例。

  • 垂直拆分:按业务维度拆分。例如将订单库、商品库、用户库分开部署在不同的 MySQL 实例上。
  • 水平拆分:按数据行拆分。例如将一张 10 亿行的订单表,按 user_id % 256 拆成 256 张结构相同的小表,每张表约 400 万行。

2.2 实现方式

分库分表本身不是数据库的能力,而是应用层或中间件层的逻辑。常见实现:

方案代表产品特点
客户端分片ShardingSphere-JDBC嵌入应用进程,无网络开销,但每个应用都需引入 SDK
代理层分片ShardingSphere-Proxy、MyCat独立部署,对应用透明,但增加一跳网络延迟
应用层硬编码自研路由逻辑灵活但维护成本极高,已基本被淘汰

2.3 分库分表的本质局限

理解分库分表的关键在于认清一个事实:它并没有改变底层数据库的单机本质。  每一个分片仍然是一个独立的 MySQL 实例,它们之间没有原生的数据协调能力。所有的"分布式"能力——路由、聚合、事务、扩容——都是在数据库外部"模拟"出来的。

这就好比:你有 16 个独立的 Excel 文件,用 Python 脚本把它们"逻辑上"当成一张大表来查询。脚本写得再好,本质上仍然是 16 个孤立文件。

三、分库分表的结构性痛点(电商场景)

回到上述电商项目,分库分表的痛点不是"功能缺失",而是架构范式本身的局限:

痛点一:跨片查询是"原罪"

电商运营的核心需求是"多维度聚合分析":按品牌、按品类、按地域、按时间窗口做销售汇总。这些查询天然需要扫描全部分片。ShardingSphere 的做法是:将 SQL 下发到所有 256 个分片,每个分片返回局部结果,中间件层做归并排序和聚合。

当分片数从 16 增长到 256 时,中间件层的内存消耗和网络 IO 呈线性增长。这不是优化能解决的——它是架构决定的上限。

痛点二:扩容是一场"战役"

分库分表的扩容意味着 Re-sharding:数据需要按照新的分片规则重新分布。对于 256 张表、总量 2TB 的订单数据,扩容到 512 张表需要:

  1. 新建 256 张目标表
  2. 全量数据迁移(按新规则路由)
  3. 增量数据追平(双写或 binlog 同步)
  4. 切换路由规则
  5. 验证数据一致性

整个过程涉及数据搬迁,存在窗口期风险。该电商团队每次扩容都需要提前两周准备,大促前一个月冻结所有变更。

痛点三:分布式事务的"税"

电商下单链路:扣库存 → 创建订单 → 扣优惠券 → 记积分,涉及 4 个不同的分片库。在分库分表架构下,这 4 个操作无法用一个本地事务保证原子性,必须引入分布式事务框架(如 Seata)。

分布式事务的代价是真实的:

  • AT 模式需要全局锁,高并发下锁冲突严重
  • TCC 模式需要业务代码实现 Try/Confirm/Cancel 三套逻辑,开发量翻倍
  • 无论哪种模式,都引入了额外的协调节点和网络往返

痛点四:DDL 变更的"长尾效应"

在 256 张分表上执行一次 ALTER TABLE ADD COLUMN,需要逐表操作。即使使用 gh-ost 做 Online DDL,也需要串行或有限并行执行,总耗时与分片数成正比。期间任何一张表出现锁等待,都可能阻塞整个变更流程。

痛点五:运维复杂度随分片数线性增长

16 个 MySQL 实例意味着:16 套主从复制、16 套备份恢复、16 套监控告警、16 套参数调优。DBA 团队不是在做"数据库管理",而是在做"批量实例管理"。

四、什么是 NewSQL?

4.1 基本概念

NewSQL 是一类在存储引擎层面原生支持分布式能力的关系型数据库。它同时具备三个特征:

  1. 关系模型:支持 SQL、ACID 事务、外键约束等传统关系型数据库特性
  2. 水平扩展:数据自动分布在多个节点上,加节点即可扩容,无需人工分片
  3. 强一致性:通过共识协议(Raft/Paxos)保证多副本数据一致,无需外部协调

4.2 与分库分表的本质区别

维度分库分表NewSQL
数据分布人工定义规则,静态存储引擎自动分裂,动态均衡
扩容Re-sharding,需数据搬迁加节点,数据自动 Rebalance
跨片查询中间件归并,性能随分片数退化优化器原生生成分布式执行计划
事务需要外部框架(Seata 等)内核原生支持分布式事务
DDL逐表执行集群级 Online DDL
对应用透明性需要引入 SDK 或 Proxy兼容 MySQL 协议,应用几乎无感

4.3 主流 NewSQL 产品

当前国内企业采用最多的两款 NewSQL 数据库是 TiDB 和 OceanBase:

  • TiDB(PingCAP):完全开源(Apache 2.0),存算分离架构,由 TiDB Server(计算)、TiKV(行存)、TiFlash(列存)、PD(调度)四个组件构成。通过 Raft 协议保证数据一致性。
  • OceanBase(蚂蚁集团):社区版开源 + 企业版商业授权,Shared-Nothing 架构,基于 LSM-Tree 存储引擎,通过 Paxos 协议保证一致性,原生支持多租户。

五、TiDB vs OceanBase:电商场景下的选型决策

该电商项目在选型阶段做了为期 3 周的 POC,核心评估维度如下:

决策因素TiDBOceanBase电商项目结论
开源程度完全开源,无商业限制社区版功能有限,企业版需付费TiDB 胜出(团队倾向自主可控)
HTAP 能力TiFlash 列存成熟,实时分析体验好以 OLTP 为主,分析需外接TiDB 胜出(运营报表需求强)
MySQL 兼容性高度兼容,少量行为差异MySQL 模式兼容度高持平
小事务延迟2-5ms(Raft 多数派确认)1-3ms(LSM-Tree 写入优化)OceanBase 略优
社区生态社区活跃,文档丰富,问题响应快社区相对小,企业级支持依赖商业TiDB 胜出
运维工具TiUP + Grafana + Prometheus,开箱即用OCP 管控平台,功能全面但学习成本高持平
多租户隔离Resource Control(较新特性)原生多租户,成熟度高OceanBase 胜出(但电商场景非刚需)

最终决策:选择 TiDB。  核心原因是 HTAP 能力(运营报表不再需要单独的 ClickHouse 链路)和完全开源带来的长期可控性。

六、迁移实战路径:五阶段方法论

该电商项目从立项到全量切换,历时约 4 个月。以下是各阶段的关键动作:

阶段一:评估与规划(3 周)

  • 导出全量慢查询日志(近 90 天),用 TiDB 的 syncer 检查工具做 SQL 兼容性扫描
  • 梳理 ShardingSphere 的分片规则:订单表 256 分片(user_id 取模)、库存表 128 分片(sku_id 取模)、优惠券表 64 分片
  • 统计跨片查询占比:约 35% 的运营后台 SQL 涉及跨片聚合
  • 容量规划:当前数据总量约 2.4TB,峰值 QPS 约 12000,规划 5 个 TiKV 节点(每节点 16C/64G/2TB NVMe SSD)

阶段二:环境搭建与全量迁移(2 周)

  • 使用 TiUP 部署集群:3 个 TiDB Server + 5 个 TiKV + 3 个 PD + 2 个 TiFlash
  • 使用 DM(TiDB Data Migration)做全量导入:256 张订单分表合并为 1 张逻辑表,DM 自动处理路由
  • 全量导入耗时约 18 小时(2.4TB 数据),期间源库正常服务

阶段三:增量同步与数据校验(2 周)

  • DM 自动衔接增量同步(基于 binlog),延迟控制在 1 秒以内

  • 数据校验分三轮:

    • 第一轮:行数比对(全量表)
    • 第二轮:抽样校验(每表随机抽 1000 行,逐字段比对)
    • 第三轮:全量 checksum(按主键范围分片计算 CRC32)
  • 发现并修复了 3 处数据不一致(均为 binlog 解析的边界 case)

阶段四:灰度切流(4 周)

采用"先读后写、先非核心后核心"的策略:

  • 第 1 周:运营后台(只读)流量 100% 切到 TiDB,验证跨片聚合查询性能
  • 第 2 周:C 端读流量(商品详情、订单查询)切 20% → 50% → 100%
  • 第 3 周:C 端写流量(下单、支付回调)切 1% → 5% → 20%
  • 第 4 周:写流量切 50% → 100%,同时停止 Seata 分布式事务,改用 TiDB 原生事务

灰度期间通过应用层的路由开关控制流量走向,异常时 30 秒内可回切到源库。

阶段五:下线旧架构与稳定性观察(3 周)

  • 停止 DM 增量同步,保留源库 2 周作为回滚保障
  • 下线 ShardingSphere 中间件和 16 个 MySQL 实例
  • 建立新的监控体系:Grafana 面板覆盖 TiKV 磁盘 IO、Raft 延迟、Region 调度、慢查询 Top 100
  • 稳定性观察期重点关注:P99 延迟变化、事务冲突率、Region 分裂频率

七、踩坑实录

坑 1:自增 ID 不再连续

原分库分表架构下,每张分表的 AUTO_INCREMENT 是独立的,业务代码中有一段逻辑用"最大 ID - 最小 ID"来估算某时间段的数据量。迁移到 TiDB 后,ID 由多个 TiDB Server 分段分配(默认 cache 30000),不再连续,该逻辑直接失效。

解决:改为基于时间戳字段做范围查询,彻底去除对 ID 连续性的依赖。

坑 2:小事务延迟上升

原架构下单条 INSERT 直接命中单个 MySQL 实例,P99 延迟约 0.8ms。迁移到 TiDB 后,一次写入需要经历 TiDB Server 解析 → TiKV 写入 → Raft 三副本确认,P99 延迟上升到 3-5ms。

对于 C 端用户感知不明显(下单接口整体 P99 从 45ms 上升到 48ms),但对于内部批量任务(如每秒 5000 次的库存扣减),吞吐量下降约 30%。

解决:批量任务改为 LOAD DATA 方式导入;高频单行写入改为攒批(每 50ms 或每 100 条提交一次)。

坑 3:统计信息不准导致执行计划走偏

迁移完成后第一天,运营后台一条报表 SQL 从原来的 8 秒变成了 120 秒。排查发现:TiDB 的统计信息是空的(全量导入不会自动收集统计信息),优化器选择了全表扫描 + Hash Join,而正确计划应该是索引扫描 + Index Join。

解决:全量导入后立即执行 ANALYZE TABLE;开启 TiDB 的自动统计信息收集(tidb_auto_analyze_ratio);对 Top 50 核心 SQL 逐一用 EXPLAIN ANALYZE 验证。

坑 4:SELECT ... FOR UPDATE 的锁行为差异

原架构中,库存扣减使用 SELECT stock FROM inventory WHERE sku_id = ? FOR UPDATE,在 MySQL 中这会加行锁 + 间隙锁。TiDB 的悲观锁模式下,锁的粒度和等待行为与 MySQL 有差异,导致部分高并发场景下出现死锁(MySQL 下不会死锁,因为间隙锁的范围不同)。

解决:将库存扣减改为 UPDATE inventory SET stock = stock - ? WHERE sku_id = ? AND stock >= ?,用条件更新代替"先查后改",彻底消除锁竞争。

坑 5:ORM 框架的隐性依赖

MyBatis 的 XML 中有一处使用了 MySQL 特有的 INSERT ... ON DUPLICATE KEY UPDATE 语法,且依赖 affected_rows 的返回值来判断是"插入"还是"更新"。TiDB 对该语句的 affected_rows 返回值语义与 MySQL 存在细微差异(TiDB 在更新时返回 2,MySQL 也返回 2,但在某些边界条件下行为不一致),导致幂等校验逻辑偶发失败。

解决:改为 INSERT IGNORE + 后续 SELECT 确认,或改用 REPLACE INTO(需评估对自增 ID 的影响)。

八、迁移后的收益与代价

可量化的收益

指标迁移前(ShardingSphere)迁移后(TiDB)
运营报表查询 P9940s+(跨 256 分片聚合)2-5s(TiFlash 列存加速)
扩容耗时2 周准备 + 4 小时窗口期加节点后自动 Rebalance,约 30 分钟
DDL 变更耗时3 小时(256 张表逐一执行)秒级(Online DDL,集群原子生效)
分布式事务Seata AT 模式,冲突率 2.3%(大促)原生事务,冲突率 < 0.1%
DBA 人力投入3 人,60% 时间维护分片1 人,日常巡检 + 容量规划
数据链路MySQL → Canal → Kafka → ClickHouse(报表)TiKV + TiFlash 一体,无需额外链路

必须正视的代价

  • 硬件成本上升:原 16 个 MySQL 实例(8C/32G/1TB HDD)替换为 5 个 TiKV 节点(16C/64G/2TB NVMe SSD)+ 3 个 TiDB Server + 3 个 PD,硬件总成本上升约 40%。但考虑到下线了 ClickHouse 集群和 Seata 服务器,整体 TCO 基本持平。
  • 学习曲线:团队花了约 2 个月时间建立对 Region 分裂、Raft 选举、统计信息、执行计划的理解。前两个月排查问题的效率明显低于 MySQL 时代。
  • 小事务延迟:对于极端延迟敏感的场景(如秒杀库存扣减),3-5ms 的写入延迟仍高于单机 MySQL 的 < 1ms。需要在应用层做攒批或异步化。

九、什么时候该迁,什么时候不该迁?

建议迁移的信号

  • 分片数已超过 64 个,且仍在增长
  • 跨片查询占比超过 20%,且需求在增加
  • 每次扩容/DDL 变更需要"立项"而非"执行"
  • 分布式事务框架已成为性能瓶颈
  • 有明确的实时分析需求(HTAP)
  • DBA 团队超过 50% 的时间在"维护架构"而非"优化业务"

不建议迁移的场景

  • 数据量 < 500GB,QPS < 5000,单实例 MySQL + 读写分离完全够用
  • 团队没有专职 DBA 或 SRE,无法承担 NewSQL 的运维学习成本
  • 业务处于 PMF(Product-Market Fit)验证期,架构稳定性优先于扩展性
  • 现有分库分表架构运行稳定,且未来 2 年无明确增长预期
  • 对单行写入延迟有极端要求(< 1ms),且无法做批量优化

十、结语

从分库分表到 NewSQL,本质上是一次复杂度转移:你把分片规则、扩容演练、跨片归并、分布式事务协调的复杂度,从应用层和中间件层转移到了数据库内核。换来的是应用代码的简洁、运维的确定性、以及弹性扩展的"日常化"。但复杂度不会消失。NewSQL 的复杂度在于 Raft/Paxos 协议、Region 调度、统计信息治理、执行计划分析。这些知识的学习成本,就是这次架构演进的"入场券"。作为架构师,我们的职责不是追逐技术潮流,而是在正确的时间、为正确的业务、做出正确的选择。分库分表不是"错误",NewSQL 也不是"银弹"。关键在于:你的业务复杂度,是否已经超过了分库分表架构所能承载的上限。 如果答案是"是",那么迁移不是"要不要做"的问题,而是"什么时候做"的问题。越早做,技术债务的利息越少。