Skip to content

分库分表

概念

分库分表是应对大规模数据和高并发场景下,将单一数据库/表拆分为多个库/表的技术方案。

  • 分库:将数据分散到多个数据库实例,突破单机连接数和资源瓶颈
  • 分表:将单张大表拆分为多张小表,降低单表数据量,提升查询效率
  • 核心目标:突破单机存储上限、提升并发吞吐、降低锁竞争

核心原理

1. 为什么要分库分表

单库单表在数据量增长到一定程度后会遇到明显瓶颈:

  • 单表数据量瓶颈:经验值为单表超过 500 万行或数据量超过 2GB,B+ 树层高增加,索引查询变慢,DDL 操作(加索引、加列)耗时极长且会锁表
  • 连接数瓶颈:MySQL 默认最大连接数约 151,高并发下连接池耗尽,请求排队甚至超时
  • 磁盘 IO 瓶颈:单机磁盘吞吐有上限,大表全表扫描会拖垮整个实例
  • 写入竞争:大量并发写同一张表,行锁/表锁冲突加剧

2. 垂直拆分 vs 水平拆分

维度垂直分库垂直分表水平分库水平分表
拆分依据按业务模块按字段冷热按数据行路由到不同库按数据行路由到不同表
解决问题耦合度高、连接数不足单行数据过宽、冷热分离单机容量/连接数瓶颈单表数据量过大
数据量变化不减少总量不减少总量每个库数据量减少每个表数据量减少
实现复杂度

垂直分库(按业务拆)

将不同业务的表拆到独立数据库,例如:订单库、用户库、商品库。各业务团队独立维护,降低耦合,但跨业务的 JOIN 变为应用层关联。

垂直分表(冷热分离)

将同一张表中访问频繁的热字段和不常访问的冷字段分离。例如用户表拆分为 user_base(id、name、phone)和 user_ext(详细地址、备注、头像 URL)。减少单行宽度,提升热数据缓存命中率。

水平分库

同一张表的数据按照分片键路由到多个库,每个库的表结构完全相同。适合需要突破单机连接数和磁盘瓶颈的场景。

水平分表

同一个库内,将一张大表拆成多张小表(如 order_0order_1、...order_N),降低单表数据量,减少索引维护开销。


3. 分片策略

Range 分片(按范围)

按时间或 ID 区间划分,如:

  • order_2024order_2025(按年)
  • user_0(id 1~100万)、user_1(id 100万~200万)

优点:便于范围查询和时间维度的数据归档
缺点:容易产生热点(最新数据集中写入最新分片),扩容时需要手动划分新区间

Hash 分片(取模)

对分片键做哈希后取模,如 shard_index = hash(user_id) % N

优点:数据分布均匀,写入压力平摊
缺点:范围查询需要扫描所有分片;扩容时取模基数变化,几乎所有数据都需要迁移

一致性哈希

将哈希值空间构建为一个环(0 ~ 2³²),节点均匀分布在环上,数据路由到顺时针方向最近的节点。

        0
       / \
    节点A   节点D
     |         |
    节点B   节点C
       \ /
      2^32-1

优点:新增/删除节点时只有相邻节点受影响,迁移数据量为 1/N
缺点:少量节点时数据分布不均,通常引入虚拟节点(Virtual Node)解决

各策略对比

策略数据均衡范围查询扩容成本热点风险
Range差(时间热点)优秀
Hash 取模差(全分片扫描)高(全量迁移)
一致性哈希较好(加虚拟节点)低(1/N 迁移)

4. 分布式 ID 方案

分库分表后不能再依赖单库自增主键,需要全局唯一 ID。

UUID

格式:550e8400-e29b-41d4-a716-446655440000

  • 优点:本地生成,无需网络调用,绝对唯一
  • 缺点:128 位无序字符串,B+ 树插入时频繁触发页分裂,写性能差;占用空间大;不可读

雪花算法(Snowflake)

Twitter 提出的 64 位有序 ID 生成方案,结构如下:

| 1位符号位 | 41位时间戳(ms) | 10位机器ID | 12位序列号 |
  • 符号位(1 bit):固定为 0,保证正数
  • 时间戳(41 bit):相对于起始时间的毫秒数,可用约 69 年
  • 机器 ID(10 bit):5 位数据中心 + 5 位机器节点,最多 1024 个节点
  • 序列号(12 bit):同一毫秒内最多生成 4096 个 ID

优点:趋势递增(对 B+ 树友好)、高性能(纯内存,每秒百万级)、含时间信息
缺点:依赖机器时钟,时钟回拨会导致 ID 重复;机器 ID 需要统一分配

号段模式(Leaf、美团方案)

从数据库预取一批 ID 号段缓存到内存,用完再取。例如 Leaf 每次从 max_id 读取并更新 max_id += step

sql
UPDATE id_alloc SET max_id = max_id + 1000, version = version + 1
WHERE biz_tag = 'order' AND version = #{old_version}
  • 优点:ID 趋势递增,DB 压力极低(每批次一次写),可用性高(双 buffer 预加载)
  • 缺点:重启后号段内未用 ID 丢失,ID 不连续;依赖 DB 高可用

5. 分库分表后的典型问题

跨库 JOIN

单库时可以直接 JOIN,分库后不同数据可能在不同实例,数据库层无法直接关联。

解法

  • 应用层关联:分两次查询,在代码中做内存 JOIN(适合数据量不大)
  • 冗余字段:在子表中冗余主表常用字段(如订单表冗余用户名),牺牲一致性换查询效率
  • 全局广播表:数据量小且几乎不变的字典表(如城市表)在每个库都保留一份完整副本

跨库事务

单库事务 ACID 天然保证;跨库后需要分布式事务:

  • 2PC(两阶段提交):Prepare 阶段加锁,Commit 阶段提交。实现简单,但协调者宕机会导致长时间阻塞,性能差
  • TCC(Try-Confirm-Cancel):业务层实现补偿逻辑,Try 预占资源,Confirm 确认,Cancel 回滚。性能好,但业务侵入性强
  • 最终一致性:通过本地消息表 + MQ 异步通知,允许短暂不一致,适合对实时性要求不高的场景

全局排序和分页

ORDER BY create_time LIMIT 10 OFFSET 100 在多分片场景下需要:

  1. 在每个分片上执行 ORDER BY create_time LIMIT 110
  2. 在应用层/中间件做归并排序,取最终 Top 10

深翻页(大 OFFSET)代价极高,建议改为游标分页(记录上次最大 ID,下次从该 ID 之后查)。

扩容困难

Hash 取模分片时,从 N 个分片扩容到 M 个分片,路由规则变化导致大量数据需要迁移。

解法

  • 一致性哈希:如前述,只迁移 1/N 数据
  • 翻倍扩容:每次扩容为原来的 2 倍(如 4 → 8),每个旧分片的数据只需拆到 2 个新分片,迁移量可控,且路由规则只需取模基数翻倍
  • 提前预分片:初始就分 1024 个逻辑分片,映射到少量物理节点,扩容时只需重新映射,数据无需迁移

6. 常见中间件

中间件厂商接入方式特点
ShardingSphereApacheJDBC / Proxy 双模式生态最完善,支持读写分离、分布式事务、数据脱敏
VitessCNCF(YouTube 开源)Proxy面向 Kubernetes,大规模 MySQL 集群管理,YouTube 生产验证
TDDL阿里巴巴JDBC阿里内部演进多年,PolarDB-X 的前身,与淘宝业务深度绑定

面试常问 & 怎么答

Q1:什么时候需要分库分表?垂直和水平怎么选?

先看瓶颈在哪里:

  • 如果是业务耦合严重、连接数不足,先考虑垂直分库,按业务域拆分
  • 如果是单表数据量过大(超过 500 万行或 2GB)导致查询慢、DDL 卡顿,考虑水平分表
  • 如果是单机资源(CPU/内存/磁盘 IO)达到上限,考虑水平分库
  • 优先顺序:垂直分库 → 读写分离 → 缓存优化 → 水平分表/分库。不要过早分片,分库分表会引入大量复杂性

Q2:分片键怎么选?选错了怎么办?

分片键选择原则:

  1. 高基数:值的种类足够多,确保数据分布均匀(不能用性别这类低基数字段)
  2. 高频出现在查询条件中:分片键必须能覆盖大多数查询,否则每次查询都要扫全分片
  3. 不可变:分片键一旦确定不应更改,否则数据需要跨分片迁移
  4. 优先选择业务主键(user_id、order_id),而非时间戳(有热点风险)

选错了怎么办:成本极高,没有低代价的方案。常见处理:

  • 双写迁移:新旧两套分片规则同时写,逐步将流量切到新规则,完成后清理旧数据
  • 停机迁移:业务低峰期停服,全量迁移,适合数据量可控的场景
  • 在线平滑迁移:配合 binlog 监听增量同步,先全量后增量,最终切流

Q3:分库分表后跨库查询怎么处理?

分三种情况:

  1. 关联查询(JOIN):优先通过冗余字段消除 JOIN 需求;如果必须关联,改为应用层两次查询后内存合并;对于字典类小表,使用广播表(每个分片都保存全量数据)
  2. 全局排序分页:中间件层归并排序,代价随页数增加而增大;业务层改为游标分页替代 OFFSET 分页
  3. 聚合统计(COUNT/SUM):每个分片并行计算局部结果,应用层汇总;对于近似统计可结合缓存或预聚合表

看到什么就先想到这类

  • 题目出现「单表数据量大、查询变慢、DDL 超时」→ 水平分表
  • 题目出现「多业务共用一个库、连接数打满」→ 垂直分库
  • 题目出现「分布式唯一 ID、主键生成」→ 雪花算法 / 号段模式
  • 题目出现「数据迁移、扩容」→ 一致性哈希 / 翻倍扩容
  • 题目出现「跨库事务」→ TCC / 最终一致性 + MQ
  • 题目出现「分页查询慢」→ 游标分页替代 OFFSET,禁止深翻页
  • 题目出现「热点写入、某分片压力大」→ 分片策略问题,考虑 Hash 分片或加盐打散