MySQL 完全指南 / 第 17 章:分库分表
第 17 章:分库分表
当单表数据量超过千万级,单库容量成为瓶颈时,分库分表是必经之路。
17.1 什么时候需要分库分表
| 指标 | 阈值 | 说明 |
|---|---|---|
| 单表行数 | > 5000 万 | 查询变慢,索引效率下降 |
| 单库数据量 | > 500GB | 备份恢复时间过长 |
| 单库写入 QPS | > 5000 | 主库压力过大 |
| 单表大小 | > 30GB | DDL 操作耗时 |
⚠️ 分库分表是最后手段,之前应先尝试:优化索引、读写分离、缓存、归档历史数据。
17.2 垂直拆分
17.2.1 垂直分库
按业务模块拆分到不同数据库。
拆分前:
app_db
├── users
├── orders
├── products
└── messages
拆分后:
user_db → users, user_profiles
order_db → orders, order_items
product_db → products, categories
msg_db → messages, notifications
17.2.2 垂直分表
将大表的列拆分为多张表。
-- 拆分前:一张表包含所有字段
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
avatar_url VARCHAR(500),
bio TEXT, -- 不常访问的大字段
settings JSON, -- 不常访问的大字段
created_at DATETIME
);
-- 拆分后:主表 + 扩展表
CREATE TABLE users (
id BIGINT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
created_at DATETIME
);
CREATE TABLE user_profiles (
user_id BIGINT PRIMARY KEY,
avatar_url VARCHAR(500),
bio TEXT,
settings JSON,
FOREIGN KEY (user_id) REFERENCES users(id)
);
17.3 水平拆分
17.3.1 水平分表(同库)
将一张大表按行拆分为多张小表。
-- 按用户 ID 取模分 4 张表
CREATE TABLE orders_0 (...);
CREATE TABLE orders_1 (...);
CREATE TABLE orders_2 (...);
CREATE TABLE orders_3 (...);
-- 路由规则:order_id % 4 = 表后缀
17.3.2 水平分库分表(不同库)
db_0.orders_0 db_0.orders_1 db_0.orders_2 db_0.orders_3
db_1.orders_0 db_1.orders_1 db_1.orders_2 db_1.orders_3
路由规则:user_id % 4 = 库后缀
order_id % 4 = 表后缀
17.3.3 分片键选择
| 分片键 | 优点 | 缺点 |
|---|---|---|
| user_id | 用户数据集中,查询高效 | 大用户问题 |
| order_id | 均匀分布 | 跨用户查询困难 |
| 时间 | 易于归档老数据 | 热点问题(集中在最新分片) |
💡 分片键选择原则:
- 选择查询频率最高的条件字段
- 数据分布均匀
- 避免跨分片查询
17.4 ShardingSphere
17.4.1 概述
Apache ShardingSphere 是最流行的分布式数据库中间件。
| 产品 | 说明 | 适用场景 |
|---|---|---|
| ShardingSphere-JDBC | Java JAR 包,嵌入应用 | Java 应用 |
| ShardingSphere-Proxy | 独立代理服务,兼容 MySQL 协议 | 任何语言 |
17.4.2 ShardingSphere-Proxy 配置
# config-sharding.yaml
schemaName: myapp
dataSources:
ds_0:
url: jdbc:mysql://192.168.1.100:3306/db_0?useSSL=false
username: root
password: root
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
ds_1:
url: jdbc:mysql://192.168.1.101:3306/db_1?useSSL=false
username: root
password: root
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
rules:
- !SHARDING
tables:
orders:
actualDataNodes: ds_${0..1}.orders_${0..3}
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: orders_inline
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
orders_inline:
type: INLINE
props:
algorithm-expression: orders_${order_id % 4}
17.4.3 分布式 ID 方案
| 方案 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 雪花算法 | 64位:时间戳+机器ID+序列号 | 有序、高性能 | 时钟回拨问题 |
| UUID | 128位全局唯一 | 简单 | 无序、占空间 |
| 号段模式 | 从数据库批量获取 ID 段 | 简单 | 数据库瓶颈 |
| Redis INCR | Redis 自增 | 高性能 | 依赖 Redis |
17.5 分库分表后的挑战
| 挑战 | 解决方案 |
|---|---|
| 跨分片 JOIN | 冗余字段、应用层组装、全局表 |
| 跨分片排序分页 | 各分片查出 N 条,应用层合并排序 |
| 跨分片聚合 | 各分片聚合后,应用层再聚合 |
| 跨分片事务 | Seata 等分布式事务框架 |
| 数据迁移 | 双写 + 对比 + 切换 |
| 全局唯一 ID | 雪花算法 |
业务场景
场景 1:订单表分库分表
分片键:user_id(按用户查询最多)
分片数:4 库 × 8 表 = 32 张表
路由:user_id % 4 = 库后缀, order_id % 8 = 表后缀