graph TD
A[业务增长] --> B{瓶颈类型}
B -->|存储瓶颈<br/>单表数据过大| C[分表]
B -->|连接/QPS瓶颈<br/>单库压力过大| D[分库]
B -->|业务耦合<br/>不同业务共享库| E[垂直分库]
C --> F[水平分表<br/>按行拆分]
D --> G[水平分库<br/>数据分散到多个库]
E --> H[按业务域拆分<br/>用户库/订单库/商品库]
什么时候考虑分库分表
指标
阈值(参考)
说明
单表行数
> 5000万
B+Tree层高增加,查询变慢
单表数据量
> 10GB
全表扫描和DDL操作耗时过长
单库QPS
> 5000
连接数和CPU接近瓶颈
单库连接数
> 3000
上下文切换开销增大
单库磁盘
> 500GB
备份恢复时间过长
分库分表策略
垂直拆分
垂直分库
按业务域将不同的表拆分到不同的数据库。
graph TD
subgraph 拆分前
DB[单一数据库] --> T1[users表]
DB --> T2[orders表]
DB --> T3[products表]
DB --> T4[payments表]
DB --> T5[inventory表]
end
subgraph 拆分后
DB1[用户库] --> T1a[users表]
DB1 --> T1b[user_profiles表]
DB2[订单库] --> T2a[orders表]
DB2 --> T2b[order_items表]
DB3[商品库] --> T3a[products表]
DB3 --> T3b[categories表]
DB4[支付库] --> T4a[payments表]
DB5[库存库] --> T5a[inventory表]
end
graph TD
subgraph "水平分表"
A[orders表<br/>1亿行] --> B[orders_0<br/>0-2499万]
A --> C[orders_1<br/>2500-4999万]
A --> D[orders_2<br/>5000-7499万]
A --> E[orders_3<br/>7500-9999万]
end
水平分库分表
将数据分散到多个数据库的多张表中。
graph TD
subgraph "水平分库分表"
A[Application] --> R[路由层]
R --> DB0[Database 0]
R --> DB1[Database 1]
R --> DB2[Database 2]
R --> DB3[Database 3]
DB0 --> T00[orders_0]
DB0 --> T01[orders_1]
DB1 --> T10[orders_2]
DB1 --> T11[orders_3]
DB2 --> T20[orders_4]
DB2 --> T21[orders_5]
DB3 --> T30[orders_6]
DB3 --> T31[orders_7]
end
分片键(Sharding Key)选择
分片键是分库分表中最关键的设计决策,直接决定了数据分布的均匀性和查询效率。
选择原则
flowchart TD
A[选择分片键] --> B{最频繁的查询条件?}
B --> C[确定候选字段]
C --> D{数据分布是否均匀?}
D -->|否| E[排除此字段]
D -->|是| F{是否支持范围查询需求?}
F -->|需要| G[考虑范围分片]
F -->|不需要| H[Hash分片]
G --> I{是否有热点问题?}
I -->|有| J[组合分片键]
I -->|无| K[确定分片键]
H --> K
J --> K
-- 使用路由表(配置表)记录分片映射关系 CREATETABLE sharding_config ( id BIGINTPRIMARY KEY, sharding_key_range_start BIGINT, sharding_key_range_end BIGINT, db_name VARCHAR(50), table_name VARCHAR(50), status TINYINT DEFAULT1 );
-- 查询路由 SELECT db_name, table_name FROM sharding_config WHERE sharding_key_range_start <=12345 AND sharding_key_range_end >12345;
分布式查询问题
跨分片查询
sequenceDiagram
participant App as Application
participant MW as Middleware
participant DB0 as DB Shard 0
participant DB1 as DB Shard 1
participant DB2 as DB Shard 2
App->>MW: SELECT * FROM orders<br/>WHERE status='paid'<br/>ORDER BY created_at DESC<br/>LIMIT 10
Note over MW: 无法确定数据在哪个分片<br/>需要广播查询
MW->>DB0: 相同查询
MW->>DB1: 相同查询
MW->>DB2: 相同查询
DB0->>MW: 10条结果
DB1->>MW: 10条结果
DB2->>MW: 10条结果
Note over MW: 合并30条结果<br/>重新排序<br/>取Top 10
MW->>App: 最终10条结果
graph TD
A[分布式ID方案] --> B[UUID]
A --> C[数据库号段]
A --> D[雪花算法 Snowflake]
A --> E[Redis自增]
B --> B1["优点: 简单,无依赖<br/>缺点: 无序,不适合做索引"]
C --> C1["优点: 简单,有序<br/>缺点: 依赖数据库"]
D --> D1["优点: 有序,高性能,不依赖外部<br/>缺点: 时钟回拨问题"]
E --> E1["优点: 简单,有序<br/>缺点: 依赖Redis"]
// 会自动路由到正确的库和表 public Order findByUserIdAndOrderId(long userId, long orderId) { return jdbcTemplate.queryForObject( "SELECT * FROM orders WHERE user_id = ? AND id = ?", newObject[]{userId, orderId}, orderRowMapper ); }
// 跨分片查询会自动广播并合并结果 public List<Order> findByStatus(String status) { return jdbcTemplate.query( "SELECT * FROM orders WHERE status = ? ORDER BY created_at DESC LIMIT 20", newObject[]{status}, orderRowMapper ); } }
数据迁移方案
双写迁移方案
sequenceDiagram
participant App as Application
participant Old as 旧单库
participant New as 新分片库
participant Sync as 同步工具
Note over App, Sync: Phase 1: 双写(新旧库同时写入)
App->>Old: 写入
App->>New: 写入(异步)
Note over Sync: 校验数据一致性
Note over App, Sync: Phase 2: 历史数据迁移
Sync->>Old: 读取历史数据
Sync->>New: 写入分片库
Note over Sync: 增量同步 + 数据校验
Note over App, Sync: Phase 3: 切读
App->>New: 读取(新库)
App->>Old: 读取(旧库作为兜底)
Note over App: 对比验证读取结果
Note over App, Sync: Phase 4: 停旧写
App->>New: 读写都走新库
Note over Old: 旧库保留观察期后下线
灰度迁移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 通过Feature Flag控制流量切换 public Order getOrder(long userId, long orderId) { if (shardingGrayConfig.isNewShardingEnabled(userId)) { // 新分片库查询 return newShardingOrderDao.findByUserIdAndOrderId(userId, orderId); } else { // 旧库查询 return oldOrderDao.findByOrderId(orderId); } }