MySQL 分库分表详解(含实践示例)
十分想念顺店杂可。。。
分库分表是应对 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(), 'Beijing', 'A developer');-- 查询用户完整信息(join两表)
SELECT c.*, e.address, e.intro
FROM `user_core` c
LEFT JOIN `user_ext` e ON c.id = e.user_id
WHERE c.username = 'zhangsan';
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(商品相关表)
-- 分库后,查询用户订单需跨库操作(伪代码示意)
-- 1. 从 user_db 查用户信息
SELECT * FROM user_db.user WHERE id = 1001;
-- 2. 从 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_idsharding-algorithm-name: order_db_inlinetable-strategy: # 分表策略standard:sharding-column: user_idsharding-algorithm-name: order_table_inlinesharding-algorithms:order_db_inline: # 分库算法(user_id%4)type: INLINEprops:algorithm-expression: order_db_${user_id % 4}order_table_inline: # 分表算法(user_id%16)type: INLINEprops: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 name="mod-long"><properties><property name="column">user_id</property><property name="count">4</property> <!-- 分4库 --></properties></ruleAlgorithm>
</rule>
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)。
实际落地中,通常采用 “垂直分库 + 水平分表” 组合,并提前规划分片规则(预留扩容空间),结合中间件降低维护复杂度。