Skip to content

MySQL 分库分表

分库分表不是一上来就该做的优化动作,也不是“数据库一慢就去拆”的通用答案。

它更像是一种架构级选择:当单库、单表已经接近容量、吞吐或维护上限时,用拆分的方式把压力分散出去。但这件事的代价也非常现实,一旦开始,查询、事务、排序、扩容、迁移、运维都会变复杂。

这篇文章重点展开 4 个方面:

  1. 什么场景下会考虑分库分表
  2. 它到底在解决什么问题
  3. 它会带来哪些新的挑战
  4. 这些挑战通常怎么处理

1. 先说结论:什么时候才值得考虑分库分表

更稳妥的顺序通常是:

  1. 先做表结构优化
  2. 再做索引优化
  3. 再做 SQL 和缓存优化
  4. 再做读写分离、主从扩展
  5. 这些都逐渐接近上限后,再认真评估分库分表

也就是说,分库分表通常不是“第一反应”,而是:单机数据库优化空间已经被吃掉后,才进入视野的架构方案。

2. 什么是分库分表

2.1 分表

分表通常指:把一张大表拆成多张更小的表。

例如把 orders 拆成:

  1. orders_00
  2. orders_01
  3. orders_02
  4. orders_03

2.2 分库

分库通常指:把数据拆到多个数据库实例中。

例如:

  1. db_order_0
  2. db_order_1
  3. db_order_2
  4. db_order_3

2.3 常见组合

真实系统里更常见的是:

  1. 先分表
  2. 再分库
  3. 或者一开始就按“分库 + 分表”一起规划

所以大家平时说“分库分表”,很多时候讨论的是一个组合动作,而不只是单独拆表。

3. 哪些场景会考虑分库分表

3.1 单表数据量过大

这是最常见的触发原因之一。

比如订单表、流水表、消息表、日志表这类业务表,如果数据持续增长,可能出现:

  1. 索引越来越大
  2. 查询和更新成本上升
  3. 历史数据维护困难
  4. DDL 变更更重

这时就会开始考虑把大表拆开。

3.2 单库写入压力过高

如果系统写流量非常大,即使索引和 SQL 都已经比较健康,单库实例也可能逐渐碰到:

  1. CPU 压力
  2. IO 压力
  3. 锁竞争压力
  4. 主库吞吐上限

这时继续靠“单库硬扛”就会越来越吃力。

3.3 业务天然具备可切分维度

比如:

  1. 按用户 ID
  2. 按商户 ID
  3. 按租户 ID
  4. 按订单号
  5. 按时间

如果业务天然存在比较稳定的路由键,那就更容易做拆分。

3.4 分布式业务场景下的高并发数据增长

在分布式系统里,服务可以横向扩展,但数据库如果仍然是单库单表,就容易成为瓶颈。

典型场景包括:

  1. 电商订单中心
  2. 支付流水系统
  3. 用户行为明细
  4. 物流轨迹
  5. 营销发券记录

这些场景往往有一个共同点:应用层已经分布式化了,但数据库层如果不继续扩展,就会卡在单点容量和吞吐上。

3.5 归档和冷热分离需求明显

有些系统并不是并发特别高,而是历史数据特别多。

这时拆分的动力可能来自:

  1. 热数据查询要快
  2. 冷数据仍然要保留
  3. 全量都放在一张表里维护成本很高

这种场景有时会走:

  1. 按时间分表
  2. 归档历史库
  3. 热冷数据分层

4. 分库分表到底解决什么问题

4.1 解决单表体量问题

单表太大后,索引、扫描、维护、归档、DDL 都会越来越重。

拆表后,每张表的数据量下降,可以带来:

  1. 单表查询范围缩小
  2. 索引体积下降
  3. 维护窗口更可控

4.2 解决单库写入瓶颈

单库再怎么调优,最终还是有天花板。

拆到多个库之后,写流量就可以分散到多个实例,缓解:

  1. 主库写压力
  2. 单点 CPU / IO 压力
  3. 热点竞争

4.3 提升整体扩展能力

分库分表的真正价值,不只是“现在更快”,而是:系统后续还能继续横向扩。

也就是说,它解决的是容量和吞吐的扩展问题,而不只是某一条 SQL 的局部性能问题。

5. 分库分表最核心的几种拆分方式

5.1 按范围拆分

例如按时间范围拆:

  1. orders_202601
  2. orders_202602
  3. orders_202603

优点:

  1. 直观
  2. 适合归档
  3. 适合冷热分离

缺点:

  1. 容易出现热点集中在最新分片
  2. 分布可能不均衡

5.2 按哈希拆分

例如:

text
user_id % 4

优点:

  1. 分布相对均匀
  2. 容易分散压力

缺点:

  1. 不适合范围查询
  2. 扩容迁移更麻烦

5.3 按业务维度拆分

例如:

  1. 按租户
  2. 按商户
  3. 按区域

优点:

  1. 业务语义清楚
  2. 很多查询天然能命中单分片

缺点:

  1. 容易出现大客户热点
  2. 数据分布未必均匀

6. 分库分表带来的挑战

这是这件事最关键的部分。

因为分库分表不是“拆完就结束”,而是“拆完之后一堆原本简单的问题开始变难”。

6.1 路由复杂度上升

拆分后,应用首先要把这件事明确下来:这条数据该落到哪个库、哪张表?

这意味着需要明确:

  1. 路由键是什么
  2. 路由规则是什么
  3. 查询时怎么命中正确分片

如果 SQL 没有带路由键,就可能出现:

  1. 无法准确定位
  2. 需要扫多个分片
  3. 查询成本迅速上升

6.2 跨分片查询更复杂

单库单表时,一条 SQL 可能直接能做完:

  1. 查询
  2. 排序
  3. 分页
  4. 聚合
  5. join

分库分表后,如果数据散在多个分片里,就可能变成:

  1. 每个分片各查一遍
  2. 应用层或中间层再合并结果
  3. 再排序、再分页、再聚合

这会直接带来更高复杂度。

6.3 跨分片事务更困难

单库事务本来由数据库自己保证,但数据一旦跨库,事务问题就会立刻复杂起来。

典型问题:

  1. 扣余额在库 A
  2. 写订单在库 B
  3. 两边怎么同时成功或同时失败

这已经不再是单机数据库事务能自然解决的问题。

6.4 全局主键设计更麻烦

单表自增主键在分库分表后不再天然全局唯一。

所以通常还要补上:

  1. 雪花算法
  2. 号段模式
  3. 发号服务

6.5 扩容与数据迁移更难

一开始按 4 个分片设计,后面如果流量继续涨,可能又要扩到 8 个、16 个。

这时就会碰到:

  1. 历史数据怎么迁
  2. 新旧路由怎么兼容
  3. 迁移期间怎么保证读写正确

这部分往往比“第一次拆分”本身还更麻烦。

6.6 运维和排障复杂度上升

数据库一多,问题就不再只是 SQL 本身,还包括:

  1. 哪个分片慢
  2. 哪个分片热点异常
  3. 哪个节点磁盘要满了
  4. 哪个分片数据倾斜

所以分库分表本质上也把运维复杂度一并抬高了。

7. 这些挑战通常怎么解决

7.1 路由问题:提前选好稳定的分片键

最常见的做法是:

  1. 选择高频查询一定会带上的字段做分片键
  2. 让大多数请求都能直接命中单分片

例如:

  1. user_id
  2. tenant_id
  3. order_id

一个好的分片键,往往能决定后续系统省掉多少复杂度。

7.2 跨分片查询:尽量少做,必要时做结果聚合

常见思路包括:

  1. 尽量让高频查询都带分片键
  2. 减少跨分片 join
  3. 应用层聚合结果
  4. 报表类查询走离线数仓、搜索、OLAP 或专门汇总表

也就是说:不要把分库分表后的数据库还当成单库来写所有查询。

7.3 跨分片事务:优先业务拆解,必要时用分布式事务

更常见、更现实的顺序通常是:

  1. 先尝试把事务收敛到单分片
  2. 再尝试用最终一致性方案拆解
  3. 实在绕不过去,再上分布式事务

对应方案包括:

  1. 本地消息表
  2. 可靠消息最终一致性
  3. TCC
  4. SAGA
  5. 两阶段提交类框架

但要注意:分布式事务不是免费的,它通常是在业务复杂度和一致性之间做更重的取舍。

7.4 全局主键:使用统一发号方案

常见方案:

  1. 雪花算法
  2. 号段模式
  3. 发号中心

要求通常包括:

  1. 全局唯一
  2. 尽量趋势递增
  3. 不依赖单分片自增

7.5 扩容迁移:预留扩展策略

为了避免后期大迁移过于痛苦,常见做法包括:

  1. 一开始就预留较多逻辑分片
  2. 逻辑分片和物理节点解耦
  3. 通过中间层或路由层做映射

这样后面增加物理节点时,不一定要重新打散全部逻辑分片。

7.6 运维治理:监控、热点识别、数据校验

分库分表后一定要补上的能力包括:

  1. 分片级监控
  2. 热点分片识别
  3. 数据迁移校验
  4. 双写 / 回切预案
  5. 容量水位监控

否则问题会从“数据库慢”变成“根本不知道哪个分片出了问题”。

8. 分布式场景下尤其容易遇到的问题

8.1 订单场景

订单中心很典型:

  1. 订单量大
  2. 查询维度多
  3. 常有订单、支付、库存、物流等多系统协同

一旦分库分表,最容易出现:

  1. 订单库和支付库跨库一致性问题
  2. 按用户查订单和按商家查订单的路由冲突
  3. 全链路排障困难

8.2 账户场景

账户余额和流水如果拆分不当,最容易触发:

  1. 跨库转账
  2. 幂等处理
  3. 重试重复扣款
  4. 账实不一致

所以账户类系统通常比普通订单系统更谨慎,不会轻易为了“扩展”就把强一致核心链路拆得太散。

8.3 多租户 SaaS 场景

多租户系统很适合按 tenant_id 拆分,但也容易出现:

  1. 大租户热点
  2. 租户迁移
  3. 跨租户汇总分析

所以“按租户拆”虽然自然,但也要考虑租户规模是否均衡。

9. 分库分表前最好先问自己的问题

真正决定要不要上分库分表之前,通常先问下面这些问题更靠谱:

  1. 当前瓶颈到底是单表、单库、单 SQL,还是缓存失效?
  2. 有没有把索引、SQL、冷热分离、主从扩展做到位?
  3. 高频查询是否天然带有稳定的分片键?
  4. 业务上能不能接受最终一致性?
  5. 团队有没有能力处理迁移、扩容、监控和数据治理?

如果这些问题还没有答案,贸然分库分表往往会把问题从“性能不够”升级成“系统失控”。

10. 一张总表:收益、挑战与常见方案

维度分库分表带来的收益带来的挑战常见解决思路
容量单表、单库压力下降逻辑变复杂合理规划逻辑分片
吞吐写流量可分散热点分片问题哈希 / 业务维度结合,热点治理
查询单分片查询更轻跨分片聚合、排序、分页变难带分片键查询,应用层聚合,报表走离线链路
事务单分片事务仍然简单跨分片事务复杂单分片优先,最终一致性,必要时分布式事务
主键可继续扩容自增主键失效雪花算法、号段模式、发号中心
扩容能横向加节点数据迁移麻烦逻辑分片预留、映射层解耦、迁移校验
运维容量更可分摊监控、排障、治理更难分片级监控、容量治理、热点识别

11. 总结

分库分表真正解决的是:

  1. 单表太大
  2. 单库压力太高
  3. 系统需要继续横向扩展

但它真正带来的代价也必须正视:

  1. 路由复杂
  2. 跨分片查询难
  3. 跨分片事务难
  4. 主键和扩容难
  5. 运维和治理难

所以更稳妥的理解不是:分库分表能让数据库更快。

而是:分库分表把单点容量问题拆开了,同时也把原本集中在单库里的复杂度分散到了应用、路由、事务和运维体系里。

真正做得好的系统,不是“拆得早”,而是:

知道什么时候该拆、为什么拆、拆完之后靠什么把复杂度兜住。

基于 VitePress 构建的个人技术笔记。