跳到主要内容

分库分表

问题

什么时候需要分库分表?垂直拆分和水平拆分有什么区别?常见的分片策略有哪些?分布式 ID 如何生成?

答案

一、为什么需要分库分表?

瓶颈表现解决方案
单表数据量过大查询变慢,索引效率下降水平分表
单库并发过高连接数不够,QPS 达上限分库
单库存储过大磁盘空间不足分库
单表字段过多大字段影响热数据查询垂直分表
分库分表的时机
  • 单表数据量超过 2000 万行或单表大小超过 10GB 时考虑(经验值,非绝对)
  • 能用读写分离、缓存、索引优化解决的,不要急于分库分表
  • 分库分表一旦实施,会带来巨大的架构复杂性

二、拆分策略

垂直拆分

垂直分表:将一张「宽表」的列拆分为多张表

原则

  • 热数据和冷数据分离
  • 大字段(TEXT/BLOB)独立分表
  • 按业务模块拆分

垂直分库:按业务域拆分到不同数据库

水平拆分

水平分表:将一张表的数据(行)按某种规则分散到多张结构相同的表

水平分库:在分表的基础上,将表分散到不同的数据库实例

三、分片策略详解

1. 取模分片(Hash)

分片编号 = sharding_key % 分片数量
优点缺点
数据均匀分布扩容困难,需要数据迁移
实现简单范围查询需要跨所有分片

2. 范围分片(Range)

分片 0: id 1 ~ 10000000
分片 1: id 10000001 ~ 20000000
分片 2: id 20000001 ~ 30000000
优点缺点
范围查询效率高可能出现热点(新数据都写最后一个分片)
扩容方便,只需添加新分片数据分布不均匀

3. 一致性哈希

通过哈希环解决取模分片扩容困难的问题。扩容时只需要迁移部分数据。

4. 按时间分片

适合日志、订单等有明显时间特征的数据:

orders_202401, orders_202402, orders_202403 ...

选择分片键的原则

原则说明
高频查询条件分片键应是大部分查询的 WHERE 条件
数据均匀避免某些分片数据过多
避免跨分片查询尽量让同一业务的查询落在同一分片

四、分布式 ID 方案

分库分表后,自增 ID 不再全局唯一,需要分布式 ID 方案:

方案原理优点缺点
UUID128 位随机简单,无依赖无序,索引效率低,占空间
雪花算法时间戳 + 机器ID + 序列号有序,高性能依赖时钟,时钟回拨问题
号段模式从 DB 批量获取 ID 段高可用,性能好依赖数据库
Redis INCR原子自增简单,高性能依赖 Redis 可用性

雪花算法(Snowflake)

0 | 0000000000 0000000000 0000000000 0000000000 0 | 00000 | 00000 | 000000000000
| 41 位时间戳 | 5位DC | 5位机器| 12 位序列号
  • 41 位时间戳:可用约 69 年
  • 10 位机器标识:最多 1024 个节点
  • 12 位序列号:每毫秒最多 4096 个 ID

号段模式(如 Leaf-Segment)

-- ID 分配表
CREATE TABLE leaf_alloc (
biz_tag VARCHAR(128) NOT NULL, -- 业务标识
max_id BIGINT NOT NULL DEFAULT 1, -- 当前最大 ID
step INT NOT NULL, -- 每次获取的 ID 数量
PRIMARY KEY (biz_tag)
);

每次从数据库获取一个号段(如 1~1000),在内存中分配,用完再取下一批。

五、分库分表带来的问题

问题说明解决方案
跨分片查询JOIN、聚合函数需要跨多个分片冗余字段,应用层聚合
跨分片事务不支持本地事务分布式事务(XA/TCC/Saga)
全局排序ORDER BY 需要归并排序中间件层归并
全局分页深度分页性能差禁止深度分页,用游标分页
ID 全局唯一自增 ID 不可用分布式 ID 方案
非分片键查询没有分片键的查询需要广播建立映射表或使用 ES
扩容困难增加分片需要数据迁移提前规划,一致性哈希

六、分库分表中间件

中间件类型特点
ShardingSphere-JDBC客户端代理(JAR)轻量,无额外部署
ShardingSphere-Proxy服务端代理语言无关,对应用透明
MyCat服务端代理功能丰富,社区活跃度下降
Vitess服务端代理YouTube 出品,支持 Kubernetes

常见面试问题

Q1: 分库分表后,不带分片键的查询怎么处理?

答案
比如订单表按 user_id 分片,但需要按 order_id 查询:

  1. 映射表:维护 order_id → user_id 的映射关系
  2. 冗余一份:额外按 order_id 维护一份索引表
  3. 搜索引擎:将数据同步到 Elasticsearch,走 ES 查询
  4. 基因法:将分片信息嵌入 ID 中(如 order_id 末几位包含 user_id 信息)

Q2: 如何实现不停机的分库分表迁移?

答案
双写方案

  1. 新增分片表,应用层同时写老表和新表
  2. 历史数据通过后台任务逐步迁移到新表
  3. 数据校验,确保老表和新表数据一致
  4. 切换读流量到新表
  5. 停止双写,废弃老表

关键点:需要有数据校验机制,保证迁移的正确性。

Q3: 分库分表和分区表有什么区别?

答案

对比项分库分表分区表
实现层应用层/中间件MySQL 内部
跨机器支持不支持(单机)
运维复杂简单
连接数分散到多个实例单实例
适用场景数据量极大,需要分散到多机器单机内的数据管理优化

Q4: 2000 万行是怎么来的?

答案
这是一个经验值。InnoDB B+ 树默认 3 层,每页 16KB:

  • 非叶子节点可存约 1170 个指针(假设键 8B + 指针 6B)
  • 叶子节点可存约 16 行(假设每行 1KB)
  • 3 层最多:1170 × 1170 × 16 ≈ 2190 万行

超过这个量,B+ 树可能变 4 层,多一次磁盘 IO,查询性能下降。但实际阈值取决于行大小。


相关链接