跳到主要内容

分库分表设计

问题

什么时候需要分库分表?水平拆分和垂直拆分有什么区别?常用的分片策略有哪些?

答案

为什么需要分库分表

瓶颈类型表现解决方案
单表数据量大查询变慢(>2000 万行)水平分表
单库连接数不够Too many connections分库
单库磁盘不够磁盘满分库
单表列太多行过宽,IO 大垂直分表
分库分表是最后手段

优先考虑:SQL 优化 → 索引优化 → 读写分离 → 缓存 → 最后才分库分表

垂直拆分

垂直分库

按业务模块拆分到不同数据库:

垂直分表

将一张宽表按列拆分为多张表:

-- 原始表(列太多,行太宽)
CREATE TABLE users (
id BIGINT, name VARCHAR(50), email VARCHAR(100),
avatar TEXT, bio TEXT, settings JSON,
-- ... 几十个字段
);

-- 垂直拆分
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
CREATE TABLE user_profiles (
user_id BIGINT PRIMARY KEY,
avatar TEXT,
bio TEXT,
settings JSON
);

原则:将常用字段和不常用字段(或大字段)分开存储。

水平拆分

将一张大表的数据按行拆分到多张结构相同的表中:

分片策略

1. 取模分片

分片编号 = sharding_key % 分片数量
-- user_id % 4 → 分到 orders_0, orders_1, orders_2, orders_3
-- user_id = 100 → 100 % 4 = 0 → orders_0
-- user_id = 101 → 101 % 4 = 1 → orders_1
优势劣势
数据均匀分布扩容需要数据迁移
实现简单分片数量一般是 2 的幂

2. 范围分片

-- 按 ID 范围分片
-- orders_0: id 1 ~ 10,000,000
-- orders_1: id 10,000,001 ~ 20,000,000
-- ...

-- 按时间范围分片
-- orders_2024Q1: 2024-01 ~ 2024-03
-- orders_2024Q2: 2024-04 ~ 2024-06
优势劣势
扩容方便(加新范围)可能数据不均匀(热点)
范围查询友好新数据集中在最后一个分片

3. 一致性 Hash

优势劣势
扩容只迁移少量数据实现相对复杂
动态增减节点需要虚拟节点防止倾斜

分片键选择

分片键(Sharding Key)的选择至关重要:

原则说明
高频查询字段大部分查询都带这个条件
数据均匀分布避免数据倾斜(热点)
避免跨分片查询尽量让一个请求只查一个分片
电商订单表常见分片键选择:
- user_id:用户维度查询为主(查我的订单)✅ 推荐
- order_id:适合按订单号精确查询
- create_time:适合按时间范围查询

分库分表带来的问题

问题说明解决方案
跨分片查询JOIN、聚合需跨多个分片冗余数据、在应用层组装
跨分片排序ORDER BY 需要归并排序中间件支持、限制查询范围
分布式事务跨库无法用本地事务分布式事务方案
全局唯一 ID自增 ID 冲突分布式 ID 生成
扩容迁移增加分片需要数据迁移预分片、一致性 Hash
运维复杂度多个库/表管理ShardingSphere 等中间件

ShardingSphere

Apache ShardingSphere 是最流行的分库分表中间件:

产品类型说明
ShardingSphere-JDBC客户端 jar轻量级,代码侵入小
ShardingSphere-Proxy独立代理对应用完全透明
# ShardingSphere 分片规则配置示例
rules:
- !SHARDING
tables:
orders:
actualDataNodes: ds_${0..1}.orders_${0..3}
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: mod
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: db_mod
shardingAlgorithms:
mod:
type: MOD
props:
sharding-count: 4

常见面试问题

Q1: 什么时候需要分库分表?

答案

单表数据量超过 2000 万行或单表大小超过 10GB 时,查询性能会明显下降。但这不是绝对标准,实际取决于:

  • 查询复杂度(简单主键查 vs 复杂 JOIN)
  • 索引设计是否合理
  • 硬件配置(SSD vs HDD)

优先级:索引优化 → 读写分离 → 缓存 → 垂直拆分 → 水平分库分表

Q2: 水平分表后,如何处理分页查询?

答案

假设 4 个分表,查询第 2 页(每页 10 条,按时间排序):

  1. 全局排序归并:每个分片都查 LIMIT 20,应用层归并排序取第 11-20 条
  2. 问题:offset 越大越慢(每个分片都要查 offset + size 条)
  3. 优化:禁止深分页,用游标分页(WHERE id > last_id LIMIT 10

Q3: 分片键选了 user_id,按 order_id 查怎么办?

答案

  1. 在 order_id 中编码分片信息:如 order_id 的最后 2 位表示分片号
  2. 建立映射表order_id → user_id 的映射表(可放 Redis)
  3. 全分片广播:向所有分片发送查询(性能最差,兜底方案)
  4. 数据冗余:按 order_id 再建一套分表(空间换时间)

Q4: 16 个库 × 64 个表够用吗?

答案

16 库 × 64 表 = 1024 个物理表:

  • 每个表 500 万行 → 总共支撑 50 亿行
  • 每个表 1000 万行 → 总共支撑 100 亿行

对于大部分互联网业务足够。关键是提前规划好分片数量,因为后续扩容成本很高。

相关链接