{T}

为什么需要分库分表,如何实现?

0. 引言

读写分离解决了读压力,但写压力单表数据量仍有天花板:单库写入 QPS 有限、单表过亿后索引深度与写入性能恶化。分库分表把数据按维度拆开——分库分散写压力,分表控制单表数据量。它是数据库扩展的终极手段,也是代价最高的手段(分布式事务、跨库查询、全局主键、扩容都随之而来)。本文讲清"何时该拆、怎么拆、拆完怎么办"。

1. 什么时候需要分库分表

text
单库连接数/写入 QPS 接近上限(如写 QPS > 5000)
单表数据量过大(如 > 2000 万 ~ 5000 万行,索引 B+ 树层数增加、DDL 锁表时间长)
单表存储空间过大(> 100GB,备份/迁移困难)

拆分的顺序与克制:先缓存 → 再读写分离 → 再分库分表。分库分表是最后手段——它带来的复杂度(事务、Join、聚合、扩容)远超前两者。

2. 拆分方式

2.1 垂直拆分(按业务/字段)

类型做法示例
垂直分库按业务域拆到不同库:用户库、订单库、商品库订单表拆到订单库
垂直分表大宽表拆成"常用字段表 + 扩展字段表"商品表拆出商品详情表
  • 优点:业务隔离清晰、单库压力下降;
  • 缺点:跨库事务(订单+库存)、跨库 Join 变应用层组装。

2.2 水平拆分(按数据行)

类型做法示例
水平分表同一库内按分片键拆成多张表order_0 ~ order_9
水平分库数据分布到多个库实例db_0 ~ db_3,各库内表结构相同
分库分表先分库再分表(库表双维度)db_0.order_0 ~ db_3.order_9
图表渲染中…

3. 分片键与分片算法

3.1 分片键选择(核心决策)

要求说明
高频查询条件查询必须带分片键才能精确定位(否则全库扫描)
数据分布均匀避免热点分片(如按用户 ID 而非订单总额)
不可变或极少变分片键变更 = 数据迁移

典型选择:订单表按 order_iduser_id 分片;若按 user_id 分片,则"查某用户订单"天然命中单分片,但"按订单查"需广播。

3.2 常见分片算法

算法实现优点缺点
取模shard = id % N简单、分布均匀扩容翻倍要全量迁移(N 变化)
范围按 ID/时间区间分片扩容友好(追加新区间)数据倾斜(热点集中在新区)
哈希hash(id) % N分布均匀同取模,扩容难
一致性哈希哈希环 + 虚拟节点扩容只影响少量数据实现复杂
基因法子表 ID 蕴含分片信息(如买家 ID 末位)无需额外映射表设计约束多

4. 中间件选型

中间件形态特点
ShardingSphere-JDBC应用内 SDK性能最好、功能全(分片+读写分离+事务);侵入应用
ShardingSphere-Proxy独立代理零侵入、多语言;多一跳
MyCat独立代理老牌、SQL 解析能力强;社区活跃度下降
VitessK8s 原生(CNCF)大规模、云原生(YouTube 出品);复杂度高

ShardingSphere 生态:JDBC(客户端)与 Proxy(服务端)可混用(Proxy 聚合多库),配合分布式事务(Seata)、分布式主键、数据迁移(ShardingSphere Migration)形成完整方案。

5. 拆分后的"代价清单"

问题影响解法
跨库查询/Join无法单库 SQL 完成冗余字段、应用层组装、宽表/汇总表
分布式事务跨库写不一致柔性事务(TCC/Seata/消息),详见事务篇
全局唯一主键自增主键冲突雪花算法/号段模式(详见下章)
聚合查询/排序分页需全分片聚合数据异构到 ES/ClickHouse 分析
扩容分片数变化需迁移一致性哈希/双写迁移(详见"如何实现扩容")
分布式 ID 与关联业务键非分片键时定位难索引表/基因法

6. 小结

  • 拆分的顺序:缓存 → 读写分离 → 分库分表,分库分表是最后手段;
  • 垂直拆(按业务/字段)解决隔离,水平拆(按分片键)解决容量;
  • 分片键决定一切:高频查询维度的均匀字段;算法选型在"均匀性"与"扩容成本"间权衡;
  • 中间件:ShardingSphere-JDBC(性能)/ Proxy(零侵入)/ Vitess(云原生);
  • 拆分前先评估代价:事务、Join、聚合、主键、扩容——拆了还能合回去吗?

下一章解决拆分后的第一个难题:分布式唯一主键方案(雪花算法与号段模式)。