分库分表

分库

分库:将数据分散到多个数据库实例中,每个实例管理一部分数据。

分库的分类:

  • 垂直分库:把单一数据库按照业务模块划分,不同的业务使用不同的数据库
  • 水平分库:把同一张表按照某个字段的值划分到不同的数据库中

分表

分表:对单表进行拆分

分表的分类:

  • 垂直分表:把单一表按照列进行拆分,不同的列放在不同的表中
  • 水平分表:把单一表按照某个字段的值进行拆分

什么情况需要分库分表

  • 单表数据量大,数据库读写速度慢
  • 数据库中数据量大,备份和恢复时间长
  • 并发访问量大,数据库连接数超过限制(应优先考虑其他性能优化方法,而非分库分表)

常见的分片算法

范围分片(Range Sharding)

按区间切。

比如按时间:

1
2
2024-01-01 ~ 2024-03-31  -> shard0
2024-04-01 ~ 2024-06-30 -> shard1

或者按 ID 范围:

1
2
0~1000万  -> shard0
1000万~2000万 -> shard1

优点:

  • 范围查询极友好
  • 按时间归档方便
  • 易于冷热分离

缺点:

  • 容易热点(最新数据打满一台机器)
  • 数据分布不均

适用于日志、订单历史、时间序列数据。

哈希分片(Hash Sharding)

最经典。

1
hash(key) % N

例如:

1
user_id % 8

优点:

  • 数据均匀
  • 实现简单
  • 避免热点

缺点:

  • 扩容灾难

%4 扩到 %8,几乎全部数据都要迁移。

一致性哈希(Consistent Hashing)

为了解决扩容问题。

最早被用在分布式缓存系统,比如 Memcached。

核心思想:

  • 把机器映射到一个哈希环上
  • key 也映射到环上
  • 顺时针找到第一台机器

优点:

  • 新增/删除节点只影响部分数据
  • 迁移成本低

缺点:

  • 实现复杂
  • 数据均衡需要“虚拟节点”优化

映射表分片

使用一个独立的映射表来记录分片键和分片位置的对应关系。

优点:

  • 灵活性高
  • 可以支持复杂的分片规则
  • 扩容相对简单

缺点:

  • 需要维护映射表
  • 查询时需要额外的映射表查询,增加了查询复杂度

地理位置分片(Geo Sharding)

按地区分。

1
2
3
华东 -> shard0
华南 -> shard1
海外 -> shard2

适合:

  • 跨国业务
  • 数据合规要求

业务维度分片(Vertical-like Horizontal Hybrid)

比如:

  • 用户数据按 user_id 分片
  • 订单数据按 user_id 分片
  • 日志按时间分片

这其实是“业务主键驱动”的分片。

很多中间件支持,例如 Apache ShardingSphere。


分片键如何选择

分片键应具备以下特点:

  • 具有共性,即大多数查询都包含这个字段,尽量减少单次查询所涉及的分片数量
  • 具有离散型,即分布均匀,避免数据倾斜和热点问题
  • 具有稳定性,即分片键的值不应频繁变更,否则会导致数据迁移和性能问题
  • 具有可扩展性,即分片键的取值范围应足够大,以支持未来数据增长

分库分表问题

分布式事务问题

单库时,InnoDB 自带 ACID。

跨库之后:

  • 本地事务失效
  • 需要分布式事务(2PC、TCC、Saga)

两阶段提交(2PC)的核心问题是:
性能差 + 阻塞 + 协调成本高。

很多公司最后都会选择:

  • 最终一致性
  • 消息队列补偿
  • 业务层幂等

强一致往往代价太高。

跨库Join失效

单库可以:

1
select * from order o join user u on ...

分库后:

  • 不知道 user 在哪个库
  • 无法直接 join

结果只能:

  1. 先查一张表
  2. 再查另一张
  3. 应用层聚合

数据库开始“退化”为 KV 存储。

全局主键问题

自增 ID 不再安全:

  • 多个库会冲突
  • 无法保证全局唯一

解决方案:

  • 雪花算法(Snowflake,最早由 Twitter 提出)
  • UUID
  • 号段模式

但这些都会带来:

  • 索引离散
  • 页分裂增多
  • 存储膨胀

扩容与数据迁移

如果你使用:

1
user_id % 4

后来变成8台机器,几乎所有数据都要重新计算并迁移。

这叫做“重分片风暴”。

一致性哈希可以缓解,但复杂度会上升。

统计查询困难

例如:

1
select count(*) from order;

现在要:

  • 所有分片分别统计
  • 再聚合

如果是:

1
order by create_time limit 10;

你需要:

  • 每个分片取前 10
  • 再做二次排序

复杂度指数上升。


总结

  • 分库就是将数据库中的数据分散到不同的数据库上。分表就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
  • 引入分库分表之后,需要系统解决事务、分布式id、无法join操作问题。
  • 现在很多公司都是用的类似于TiDB这种分布式关系型数据库,不需要我们手动进行分库分表(数据库层面已经帮我们做了),也不需要解决手动分库分表引入的各种问题,直接一步到位,内置很多实用的功能(如无感扩容和缩容、冷热存储分离)!如果公司条件允许的话,个人也是比较推荐这种方式!
  • 如果必须要手动分库分表的话,ShardingSphere是首选!ShardingSphere的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理等功能。另外,ShardingSphere的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。