MySQL 分库分表实战
# MySQL 分库分表实战
数据量到了千万级,单表扛不住了,就要分库分表。这篇说说怎么做。
## 什么时候需要分库分表?
```
单表数据量:
- < 500万:不用分,加索引、优化 SQL
- 500万~2000万:考虑分表
- > 2000万:建议分表
单库容量:
- < 50GB:不用分库
- 50GB~200GB:考虑分库
- > 200GB:建议分库
```
## 分库 vs 分表
| 维度 | 分库 | 分表 |
|------|------|------|
| 对象 | 数据库级别 | 表级别 |
| 解决 | 单库连接数、IO瓶颈 | 单表数据量瓶颈 |
| 代价 | 跨库事务、JOIN困难 | 路由复杂 |
| 建议 | 先分表,不够再分库 | 优先考虑 |
## 垂直拆分
### 垂直分库:按业务拆库
```
mydb(单库)
├── user、order、product、log 全在一个库
↓
user_db (用户库)
order_db (订单库)
product_db (商品库)
log_db (日志库)
```
### 垂直分表:按字段拆表
```sql
-- user 表字段太多,拆成两张
-- user_base:常用字段
CREATE TABLE user_base (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
phone VARCHAR(20),
status TINYINT
);
-- user_ext:不常用字段
CREATE TABLE user_ext (
user_id BIGINT PRIMARY KEY,
bio TEXT,
avatar_url VARCHAR(200),
created_at DATETIME
);
```
## 水平拆分
### 水平分表:数据按规则分布到多张表
```
order 表 1000万数据 → 按 user_id 分成 4 张表
order_0:user_id % 4 = 0
order_1:user_id % 4 = 1
order_2:user_id % 4 = 2
order_3:user_id % 4 = 3
```
### 分片策略
| 策略 | 规则 | 优点 | 缺点 |
|------|------|------|------|
| Hash | user_id % N | 数据均匀 | 扩容麻烦 |
| Range | 按时间/ID范围 | 范围查询方便 | 数据可能不均匀 |
| 一致性Hash | Hash环 | 扩容只迁移部分 | 实现复杂 |
## ShardingSphere 实战
### Maven 依赖
```xml
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.0</version>
</dependency>
```
### 配置水平分表
```yaml
spring:
shardingsphere:
datasource:
names: ds0
ds0:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
rules:
sharding:
tables:
order:
actual-data-nodes: ds0.order_$->{(0..3)}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-mod
sharding-algorithms:
order-mod:
type: MOD
props:
sharding-count: 4
props:
sql-show: true
```
```java
// 使用和普通 MyBatis 一样
@Mapper
public interface OrderMapper {
@Insert("INSERT INTO `order` (user_id, amount) VALUES (#{userId}, #{amount})")
void insert(Order order);
@Select("SELECT * FROM `order` WHERE user_id = #{userId}")
List<Order> selectByUserId(Long userId);
}
// ShardingSphere 自动路由到 order_0~3
```
### 配置水平分库
```yaml
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
jdbc-url: jdbc:mysql://192.168.1.100:3306/mydb
ds1:
jdbc-url: jdbc:mysql://192.168.1.101:3306/mydb
rules:
sharding:
tables:
order:
actual-data-nodes: ds$->{0..1}.order_$->{(0..3)}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-mod
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: table-mod
sharding-algorithms:
db-mod:
type: MOD
props:
sharding-count: 2
table-mod:
type: MOD
props:
sharding-count: 4
```
## 分布式ID
分库分表后,不能用自增主键了。
### 1. 雪花算法(推荐)
```java
// ShardingSphere 内置
// 配置:
spring:
shardingsphere:
rules:
sharding:
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
// 使用:
public class Order {
@TableId(type = IdType.ASSIGN_ID)
private Long id; // 自动生成分布式ID
}
```
### 2. UUID
```sql
SELECT UUID(); -- 36 位字符串
-- 缺点:太长、无序、索引性能差
```
### 3. Leaf(美团)
```
基于数据库号段 + ZooKeeper 的分布式ID方案
优点:高性能、趋势递增
```
## 分库分表后的 JOIN 问题
```sql
-- ❌ 跨表 JOIN 在 ShardingSphere 中有限制
SELECT * FROM order o JOIN user u ON o.user_id = u.id;
-- 如果 order 和 user 不在同一个库/表,无法直接 JOIN
-- ✅ 方案1:绑定表(分片键相同)
spring:
shardingsphere:
rules:
sharding:
binding-tables:
- order, order_item # 同一个 user_id 分片
-- ✅ 方案2:应用层组装
List<Order> orders = orderMapper.selectByUserId(userId);
List<Long> productIds = orders.stream().map(Order::getProductId).collect(Collectors.toList());
List<Product> products = productMapper.selectByIds(productIds);
// 内存中关联
-- ✅ 方案3:全局表(字典表广播到每个库)
spring:
shardingsphere:
rules:
sharding:
broadcast-tables:
- dict, config # 每个库都有完整数据
```
## 分页问题
```sql
-- ❌ LIMIT 在分表场景下有问题
SELECT * FROM order ORDER BY id LIMIT 1000000, 10;
-- 需要查询所有分片取 Top N,再合并,性能很差
-- ✅ 方案1:用上次的最大 ID
SELECT * FROM order WHERE id > #{lastId} ORDER BY id LIMIT 10;
-- ✅ 方案2:禁止深度分页
-- 业务层面限制只能翻前 100 页
```
## 扩容问题
```
从 4 张表扩到 8 张表怎么办?
- Hash 取模扩容:数据需要全量迁移
- 一致性Hash扩容:只迁移部分数据
推荐:初始分片数留余量(比如初始 16 张表,先只用 4 张)
```
## 小结
| 问题 | 解决方案 |
|------|---------|
| 拆分方式 | 先垂直再水平 |
| 分片策略 | Hash取模(简单) |
| 分布式ID | 雪花算法 |
| 跨表JOIN | 绑定表/应用层/全局表 |
| 分页 | 避免深度分页,用游标 |
| 扩容 | 初始留余量 |
---
**相关阅读:**
- [MySQL 读写分离实战]
- [MySQL 集群方案对比]
- [MySQL 一千万级数据分页查询]
