分库分表是应对 MySQL 单库单表瓶颈的核心手段,通过拆分数据分散存储和访问压力。本文从拆分维度、实现方式、实践示例到关键挑战进行全面梳理,帮助理解和落地分库分表方案。
一、按拆分维度分类
1. 垂直分表(Column Sharding)
定义
将单表中字段按访问频率和关联性拆分到多个表,各表共享同一主键,业务上属于同一主体。
适用场景
- 单表字段过多(如超过 50 个),查询时加载冗余字段导致 IO 效率低;
- 存在大字段(
text、blob)或低频访问字段,拖慢核心查询; - 字段访问频率差异显著(如用户核心信息 vs 扩展资料)。
优缺点
| 优点 | 缺点 |
|---|---|
| 减少单表字段数,降低 IO 成本 | 需维护多表关联,增加 join 操作 |
| 核心字段与大字段分离,提升查询效率 | 跨表查询增加业务复杂度 |
| 表结构更贴合业务模块 | - |
实践示例
-- 原用户表(字段冗余)
CREATE TABLE `user` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`username` varchar(50) NOT NULL, -- 高频
`password` varchar(100) NOT NULL, -- 高频
`age` int, -- 高频
`address` text, -- 低频 + 大字段
`intro` text, -- 低频 + 大字段
`create_time` datetime NOT NULL
);
-- 拆分后:核心信息表(高频访问)
CREATE TABLE `user_core` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`age` int,
`create_time` datetime NOT NULL
);
-- 拆分后:扩展信息表(低频访问)
CREATE TABLE `user_ext` (
`user_id` bigint PRIMARY KEY, -- 与 user_core.id 关联
`address` text,
`intro` text,
FOREIGN KEY (`user_id`) REFERENCES `user_core`(`id`) ON DELETE CASCADE
);
-- 插入数据(需同时写入两表)
INSERT INTO `user_core` (username, password, age, create_time) VALUES ('zhangsan', 'hash_pwd', 25, '2023-01-01 08:00:00');
INSERT INTO `user_ext` (user_id, address, intro) VALUES (LAST_INSERT_ID(), , );
c., e.address, e.intro `user_core` c `user_ext` e c.id e.user_id c.username ;
2. 垂直分库(Database Sharding by Business)
定义
将不同业务模块的数据库拆分到独立实例,各库无直接关联,按业务边界隔离。
适用场景
- 单库承载多业务,出现 CPU、内存或 IO 瓶颈;
- 业务模块访问频率差异大(如订单库并发远高于商品库);
- 业务耦合度低,可独立扩展(如用户、订单、商品模块)。
优缺点
| 优点 | 缺点 |
|---|---|
| 分散单库压力,各库可独立配置资源 | 跨库业务需处理分布式事务 |
| 业务隔离,某库故障不影响其他模块 | 全局表(如字典表)需冗余维护 |
| 便于按业务分工维护 | 跨库 join 困难 |
实践示例
-- 原单库架构:all_in_one_db(包含用户、订单、商品表)
all_in_one_db
├─ user(用户表)
├─ order(订单表)
└─ product(商品表)
-- 垂直分库后:按业务拆分 3 个独立库
user_db(用户库)
└─ user(用户相关表)
order_db(订单库)
└─ order(订单相关表)
product_db(商品库)
└─ product(商品相关表)
分库后,查询用户订单需跨库操作(伪代码示意):
- 从 user_db 查用户信息:
SELECT * FROM user_db.user WHERE id = 1001; - 从 order_db 查订单信息:
SELECT * FROM order_db.order WHERE user_id = 1001;
3. 水平分表(Row Sharding)
定义
将单表中数据按规则拆分到多个结构相同的表,各表字段一致,数据不同(按行拆分)。
拆分规则
| 规则类型 | 说明 | 适用场景 | 示例 |
|---|---|---|---|
| 范围拆分 | 按数值 / 时间范围拆分 | 时间序列数据(日志、订单) | 订单表按月份分表:order_202301、order_202302 |
| 哈希拆分 | 按关键字哈希取模 | 数据访问均匀(用户表) | 用户表按 user_id%4 分 4 表:user_0~user_3 |
| 列表拆分 | 按枚举值拆分 | 数据有明确分类(地区、状态) | 订单表按地区分表:order_beijing、order_shanghai |
| 复合拆分 | 结合多种规则 | 复杂场景(大促订单) | 先按时间范围,再按用户 ID 哈希 |
适用场景
- 单表数据量过大(超 1000 万行),查询 / 索引维护效率低;
- 单表写入压力高(每秒数千条 insert),磁盘 IO 瓶颈明显。
优缺点
| 优点 | 缺点 |
|---|---|
| 降低单表数据量,提升读写效率 | 跨表查询需聚合结果(复杂) |
| 可按需扩展表数量 | 分表规则变更需迁移全量数据 |
| 各分表可独立优化(索引、存储) | 需维护路由逻辑 |
实践示例
示例 1:按时间范围分表(订单表)
-- 2023 年 1 月订单表
CREATE TABLE `order_202301` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`order_no` varchar(32) UNIQUE NOT NULL,
`user_id` bigint NOT NULL,
`amount` decimal(10,2) NOT NULL,
`create_time` datetime NOT NULL,
INDEX idx_user_id (`user_id`)
);
-- 2023 年 2 月订单表(结构与 order_202301 一致)
CREATE TABLE `order_202302` (...);
-- 插入数据(按 create_time 路由到对应表)
INSERT INTO `order_202301` (order_no, user_id, amount, create_time) VALUES ('ORD20230105001', 1001, 99.99, '2023-01-05 10:30:00');
示例 2:按哈希拆分(用户表)
-- 按 user_id%4 分 4 张表(user_0~user_3)
CREATE TABLE `user_0` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`user_id` bigint UNIQUE NOT NULL, -- 哈希分表键
`username` varchar(50) NOT NULL,
`create_time` datetime NOT NULL
);
CREATE TABLE `user_1` (...); -- 结构与 user_0 一致
CREATE TABLE `user_2` (...);
CREATE TABLE `user_3` (...);
-- 插入数据(按 user_id 哈希路由)
-- user_id=1001:1001%4=1 → 插入 user_1
INSERT INTO `user_1` (user_id, username, create_time) VALUES (1001, 'zhangsan', '2023-01-01 08:00:00');
4. 水平分库(Database Sharding by Data)
定义
将水平分表后的表分布到多个数据库实例,同时分散库和表的压力(分库 + 分表结合)。
适用场景
- 水平分表后单库压力仍过大(CPU / 内存 / 连接数瓶颈);
- 需支持更高并发(多库可同时处理更多请求)。
优缺点
| 优点 | 缺点 |
|---|---|
| 同时分散库和表的压力,支持高并发 | 架构复杂,需管理多库多表路由 |
| 可按库隔离资源(热点库单独配置) | 跨库事务和 join 难度大 |
实践示例
-- 分 4 个库,每个库含 4 张订单表(共 4 库×4 表=16 分片)
order_db_0(库 0)
├─ order_0(user_id%16=0)
├─ order_1(user_id%16=1)
├─ order_2(user_id%16=2)
└─ order_3(user_id%16=3)
order_db_1(库 1)
├─ order_4(user_id%16=4)
├─ order_5(user_id%16=5)
├─ order_6(user_id%16=6)
└─ order_7(user_id%16=7)
order_db_2(库 2)→ 存储 8-11 分片
order_db_3(库 3)→ 存储 12-15 分片
-- 路由规则:
-- 库索引 = user_id % 4
-- 表索引 = user_id % 16
-- 例:user_id=1001 → 1001%4=1(库 1),1001%16=9(表 9)
INSERT INTO `order_db_1.order_9` (order_no, user_id, amount) VALUES ('ORD20230501001', 1001, 199.99);
二、按实现方式分类
1. 客户端方案(嵌入式分片)
原理
在应用中集成分片逻辑(如 JDBC 驱动扩展),直接路由请求到目标库表。
代表工具
Sharding-JDBC(Apache ShardingSphere)
实现流程
- 应用配置分片规则(分库键、分表键、路由算法);
- Sharding-JDBC 拦截 SQL,解析后路由到目标库表;
- 聚合结果返回给应用。
优缺点
| 优点 | 缺点 |
|---|---|
| 无中间件,性能损耗低(仅 SQL 解析) | 与应用耦合,需在应用层维护规则 |
| 架构简单(应用与数据库直连) | 多语言支持差(主要支持 Java) |
| - | 扩容需重启应用更新配置 |
示例配置(Sharding-JDBC)
# 分片规则配置(简化版)
spring:
shardingsphere:
rules:
sharding:
tables:
t_order: # 逻辑表名
actual-data-nodes: order_db_${0..3}.order_${0..15} # 实际分片
database-strategy: # 分库策略
standard:
sharding-column: user_id
sharding-algorithm-name: order_db_inline
table-strategy: # 分表策略
standard:
sharding-column: user_id
sharding-algorithm-name: order_table_inline
sharding-algorithms:
order_db_inline: # 分库算法(user_id%4)
type: INLINE
props:
algorithm-expression: order_db_${user_id % 4}
order_table_inline: # 分表算法(user_id%16)
type: INLINE
props:
algorithm-expression: order_${user_id % 16}
2. 中间件方案(代理层分片)
原理
在应用与数据库间部署代理服务,统一处理路由、聚合、事务等逻辑。
代表工具
MyCat、ShardingSphere-Proxy、ProxySQL
实现流程
- 应用连接代理(如 MyCat),按单库单表方式写 SQL;
- 中间件解析 SQL,按规则路由到目标库表;
- 聚合结果返回给应用。
优缺点
| 优点 | 缺点 |
|---|---|
| 与应用解耦,支持多语言(标准 MySQL 协议) | 中间件可能成为性能瓶颈(需集群部署) |
| 集中管理分片规则,扩容无需改应用 | 额外网络开销(应用→中间件→数据库) |
| 可扩展读写分离、容灾等功能 | - |
示例(MyCat 逻辑表配置)
<!-- MyCat schema.xml 配置 -->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
<!-- 逻辑表 t_order,对应物理分片 -->
<table name="t_order" dataNode="dn$0-3" rule="order_sharding" />
</schema>
<!-- 数据节点(分库) -->
<dataNode name="dn0" dataHost="host1" database="order_db_0" />
<dataNode name="dn1" dataHost="host1" database="order_db_1" />
<dataNode name="dn2" dataHost="host2" database="order_db_2" />
<dataNode name="dn3" dataHost="host2" database="order_db_3" />
<!-- 分片规则(user_id 哈希) -->
<rule name="order_sharding">
<ruleAlgorithm =>
user_id
4
3. 数据库原生方案(分区表)
原理
MySQL 原生支持分区表,物理上拆分到多个文件,逻辑上为单表,数据库自动管理路由。
分区类型
- 范围分区(RANGE):按范围拆分(如时间、数值);
- 列表分区(LIST):按枚举值拆分(如地区);
- 哈希分区(HASH):按哈希值拆分;
- 键分区(KEY):基于 MySQL 内部哈希函数。
优缺点
| 优点 | 缺点 |
|---|---|
| 对应用透明,无需修改代码 | 仍属单库,无法分散实例压力(CPU / 内存瓶颈) |
| 维护简单(MySQL 自动管理) | 分区数有限制(过多影响性能) |
| 支持分区级操作(如删除历史分区) | 不支持跨分区全局索引 |
实践示例(RANGE 分区)
-- 订单表按季度分区
CREATE TABLE `order_partition` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`order_no` varchar(32) UNIQUE NOT NULL,
`user_id` bigint NOT NULL,
`amount` decimal(10,2) NOT NULL,
`create_time` datetime NOT NULL,
INDEX idx_user_id (`user_id`)
) PARTITION BY RANGE (TO_DAYS(create_time)) (
PARTITION p2023Q1 VALUES LESS THAN (TO_DAYS('2023-04-01')),
PARTITION p2023Q2 VALUES LESS THAN (TO_DAYS('2023-07-01')),
PARTITION p2023Q3 VALUES LESS THAN (TO_DAYS('2023-10-01')),
PARTITION p2023Q4 VALUES LESS THAN (TO_DAYS('2024-01-01'))
);
-- 插入数据(自动路由到对应分区)
INSERT INTO `order_partition` (order_no, user_id, amount, create_time) VALUES ('ORD20230501001', 1001, 199.99, '2023-05-01 14:30:00');
-- 进入 p2023Q2
-- 查询指定分区(优化性能)
SELECT * FROM `order_partition` PARTITION (p2023Q2) WHERE user_id = 1001;
三、关键挑战与解决方案
| 挑战 | 解决方案 |
|---|---|
| 跨库 join | 1. 数据冗余(如订单表冗余商品名称);2. 应用层组装(先查 A 库,再查 B 库,内存 join);3. 中间件支持(MyCat 全局表、ER 表) |
| 分布式事务 | 1. 最终一致性(消息队列 + 本地事务);2. 2PC 协议(Seata);3. TCC 补偿机制 |
| 全局 ID | 1. 雪花算法(Snowflake);2. 数据库自增 ID 分段(ShardingSphere 分布式 ID);3. Redis 自增 |
| 扩容迁移 | 1. 双写迁移(旧库和新库同时写入,校验一致后切换);2. 中间件弹性伸缩(ShardingSphere) |
四、总结
分库分表需结合业务场景选择方案:
- 字段多、访问频率差异大 → 垂直分表;
- 业务模块独立、单库压力大 → 垂直分库;
- 单表数据量过大(千万级 +) → 水平分表;
- 分表后单库仍压力大 → 水平分库;
- 轻量场景、不愿引入中间件 → MySQL 分区表;
- 高并发、多语言 → 中间件方案(MyCat/ShardingSphere-Proxy);
- 性能敏感、Java 应用 → 客户端方案(Sharding-JDBC)。
实际落地中,通常采用'垂直分库 + 水平分表'组合,并提前规划分片规则(预留扩容空间),结合中间件降低维护复杂度。


