分库分表是应对MySQL单机单库数据量过大、性能瓶颈的重要解决方案。当数据库中的数据量达到一定规模(通常千万级以上)或并发访问量过高时,单机数据库会面临存储容量不足、查询性能下降、IO压力过大等问题。分库分表通过将数据分散到多个数据库或表中,实现数据的水平扩展,提升系统的吞吐量和可用性。
一、分库分表的基本概念
1. 什么是分库分表
- 分表:将一个大表按照某种规则拆分成多个小表,每个小表存储一部分数据
- 分库:将一个大数据库按照某种规则拆分成多个小数据库,每个小数据库存储一部分数据
- 通常分库和分表会结合使用,形成分布式数据库架构
2. 分库分表的目的
- 解决单表数据量过大问题,提高查询性能
- 分散数据库压力,提高并发处理能力
- 突破单机存储容量限制,支持数据规模增长
- 提高系统可用性,单个分库或分表故障不影响整体系统
3. 分库分表的挑战
- 分布式事务一致性问题
- 跨库联合查询复杂
- 全局唯一ID生成困难
- 数据迁移和扩容复杂
- 应用程序需要适配分库分表逻辑
二、分库分表的方式
1. 水平拆分(Horizontal Sharding)
水平拆分是将表中的行按照某种规则分散到不同的表或库中,每个表或库的结构相同,但数据不同。
(1)特点
- 每个分表/分库的结构与原表完全一致
- 数据按行拆分,不改变表结构
- 适用于数据量过大的表,可无限扩展(理论上)
(2)常见拆分规则
① 范围拆分:
按时间、ID等范围进行拆分,如按年份拆分订单表:
- orders_2021:存储2021年的订单
- orders_2022:存储2022年的订单
- orders_2023:存储2023年的订单
② 哈希拆分:
对关键字段进行哈希计算,根据哈希结果分配到不同分表,如按用户ID哈希:
table_index = user_id % 4
将用户ID哈希后模4,分配到users_0、users_1、users_2、users_3四个表中
③ 地理位置拆分:
按用户所在地区拆分,如按省份拆分用户表:
- users_beijing、users_shanghai、users_guangdong等
④ 业务维度拆分:
按业务线或客户类型拆分,如电商系统按客户等级拆分:
- users_vip、users_normal、users_new等
2. 垂直拆分(Vertical Sharding)
垂直拆分是将表中的列按照某种规则分散到不同的表或库中,每个表或库的结构不同,但共同组成原表的完整数据。
(1)特点
- 每个分表/分库的结构不同,包含原表的部分字段
- 数据按列拆分,拆分后表结构发生变化
- 适用于表字段过多或不同字段访问频率差异大的场景
(2)常见拆分规则
① 按访问频率拆分:
将常用字段和不常用字段拆分为不同表:
- users_basic:存储用户ID、姓名、手机号等常用信息
- users_extra:存储用户简介、爱好、注册IP等不常用信息
② 按字段大小拆分:
将大字段拆分到单独表中,如:
- articles:存储文章ID、标题、发布时间等
- articles_content:存储文章ID和正文内容(大字段)
③ 按业务职责拆分:
将不同业务模块的数据拆分到不同库中,如电商系统:
- user_db:用户相关表
- order_db:订单相关表
- product_db:商品相关表
- payment_db:支付相关表
3. 混合拆分
实际应用中,通常会结合水平拆分和垂直拆分:
① 先进行垂直拆分,按业务模块拆分不同的库
② 对每个库中数据量大的表再进行水平拆分
例如:电商系统先垂直拆分为用户库、订单库、商品库,然后对订单库中的订单表按时间进行水平拆分
三、分库分表的实现方案
1. 客户端解决方案(代码层分片)
在应用程序代码中实现分库分表逻辑,直接操作多个数据库实例。
(1)实现方式
- 自定义分片逻辑:在DAO层根据分片规则路由到相应的库和表
- 使用ORM框架扩展:如MyBatis的插件机制实现分库分表
(2)示例代码(伪代码)
// 哈希分片示例
public class OrderDAO {
// 订单分表数量
private static final int TABLE_COUNT = 8;
public Order getOrder(Long orderId) {
// 计算分表索引
int tableIndex = (int)(orderId % TABLE_COUNT);
String tableName = "orders_" + tableIndex;
// 执行查询
return jdbcTemplate.queryForObject(
"SELECT * FROM " + tableName + " WHERE id = ?",
new Object[]{orderId},
new OrderRowMapper()
);
}
public void saveOrder(Order order) {
// 计算分表索引
int tableIndex = (int)(order.getId() % TABLE_COUNT);
String tableName = "orders_" + tableIndex;
// 执行插入
jdbcTemplate.update(
"INSERT INTO " + tableName + " (id, user_id, amount) VALUES (?, ?, ?)",
order.getId(), order.getUserId(), order.getAmount()
);
}
}
(3)优缺点
优点:
- 实现简单,无需引入额外组件
- 性能较好,无中间层开销
- 灵活性高,可根据业务定制分片规则
缺点:
- 分片逻辑与业务代码耦合
- 维护成本高,修改分片规则需修改代码
- 跨库事务和联合查询实现复杂
- 不支持动态扩容
2. 中间件解决方案
通过独立的中间件实现分库分表逻辑,应用程序通过中间件访问数据库,无需关心分片细节。
(1)主流中间件
① ShardingSphere(开源):
- 包含Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar
- 功能全面,支持多种分片策略、分布式事务、读写分离等
- 社区活跃,文档丰富
② MyCat(开源):
- 基于Cobar开发,成熟稳定
- 支持多种分片规则,提供监控功能
- 采用代理模式,对应用透明
③ 其他中间件:
- Atlas(奇虎360开源)
- DDB(阿里云分布式数据库服务)
- TDSQL(腾讯分布式数据库)
(2)Sharding-JDBC配置示例
// application.yml 配置
spring:
shardingsphere:
datasource:
names: db0, db1
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db0
username: root
password: 123456
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db1
username: root
password: 123456
rules:
sharding:
tables:
t_order:
actual-data-nodes: db${0..1}.t_order${0..3}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order_db_inline
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: order_table_inline
sharding-algorithms:
order_db_inline:
type: INLINE
props:
algorithm-expression: db${user_id % 2}
order_table_inline:
type: INLINE
props:
algorithm-expression: t_order${order_id % 4}
props:
sql-show: true
(3)优缺点
优点:
- 分片逻辑与业务代码解耦
- 功能丰富,支持复杂场景
- 便于统一管理和维护
- 支持动态扩容和配置变更
缺点:
- 引入中间件增加系统复杂度
- 存在一定性能开销(代理模式)
- 学习和配置成本较高
- 部分复杂SQL可能不支持
3. 数据库厂商解决方案
一些数据库厂商提供了原生的分库分表支持:
- MySQL NDB Cluster:MySQL官方集群方案,支持自动分片
- 阿里云PolarDB:兼容MySQL,支持自动分库分表
- 腾讯云TDSQL:基于MySQL的分布式数据库
- 华为云GaussDB:支持分布式部署和自动分片
四、分库分表的关键问题
1. 全局唯一ID生成
分库分表后,自增ID无法保证全局唯一,需要分布式ID生成方案:
① UUID/GUID:
String id = UUID.randomUUID().toString().replaceAll("-", "");
// 生成类似:550e8400e29b41d3a476d96b66d08491
优点:实现简单,无需中心化服务
缺点:无序,长度长,索引性能差
② 雪花算法(Snowflake):
64位ID,包含时间戳、机器ID、序列号等信息,有序递增
// 伪代码
public class SnowflakeIdGenerator {
private final long workerId; // 机器ID
private final long datacenterId; // 数据中心ID
private long sequence = 0L; // 序列号
private long lastTimestamp = -1L; // 上次生成ID的时间戳
// 生成ID的方法
public synchronized long nextId() {
// 实现逻辑...
}
}
优点:有序,性能高,可手动设置机器ID
缺点:依赖系统时钟,时钟回拨会导致ID重复
③ 数据库自增ID表:
-- 创建ID生成表
CREATE TABLE sequence (
name VARCHAR(50) NOT NULL PRIMARY KEY,
value BIGINT NOT NULL
);
-- 插入初始值
INSERT INTO sequence (name, value) VALUES ('order_id', 1);
-- 获取下一个ID
REPLACE INTO sequence (name, value) VALUES ('order_id', LAST_INSERT_ID(value + 1));
SELECT LAST_INSERT_ID();
优点:实现简单,ID有序递增
缺点:单点故障风险,性能瓶颈
④ 其他方案:
- Redis自增:利用INCR命令生成ID
- 号段模式:预分配一段ID,减少数据库访问
2. 分布式事务
分库分表后,跨库操作需要保证事务一致性:
① 两阶段提交(2PC):
通过协调者协调所有参与者,分准备和提交两个阶段完成事务
优点:强一致性
缺点:性能差,存在阻塞风险,协调者单点问题
② 补偿事务(TCC):
将事务拆分为Try、Confirm、Cancel三个操作,通过补偿机制保证最终一致性
优点:性能好,无锁阻塞
缺点:业务侵入性强,实现复杂
③ 本地消息表:
通过本地事务记录消息,异步通知其他服务,保证最终一致性
优点:实现简单,可靠性高
缺点:有一定延迟,需要处理消息重复问题
④ SAGA模式:
将长事务拆分为多个短事务,每个短事务有对应的补偿事务
优点:适合长事务场景,可扩展性好
缺点:实现复杂,一致性保障较弱
⑤ 中间件方案:
ShardingSphere支持XA事务和SEATA事务,简化分布式事务实现
3. 跨库联合查询
分库分表后,JOIN查询变得复杂,解决方案包括:
① 避免跨库JOIN:通过合理设计分片规则,将相关数据放在同一库表
② 应用层聚合:先查询各分库数据,再在应用程序中进行JOIN操作
// 伪代码:应用层聚合
List orders = orderDAO.queryByUserId(userId); // 查询用户所有订单
for (Order order : orders) {
// 查询每个订单的商品信息
List products = productDAO.queryByOrderId(order.getId());
order.setProducts(products);
}
return orders;
③ 数据冗余:适当冗余数据,减少JOIN需求
④ 中间件支持:部分中间件(如ShardingSphere)支持跨库JOIN
4. 数据迁移与扩容
分库分表后的数据迁移和扩容需要特别处理:
① 停机迁移:
停止服务,迁移数据,修改配置,重启服务
优点:简单可靠
缺点:服务中断,影响可用性
② 在线迁移:
通过工具实现不停机数据迁移,如:
- Percona XtraBackup:热备份工具
- ShardingSphere Migration:数据迁移工具
- 自定义迁移程序:双写数据,校验一致性后切换
③ 平滑扩容:
采用一致性哈希等策略,减少扩容时的数据迁移量
例如:从4个分表扩容到8个分表,只需迁移一半数据
五、分库分表的最佳实践
1. 拆分策略选择
- 优先考虑垂直拆分,按业务边界划分数据库
- 水平拆分选择合适的分片键,应满足:
1. 访问频率高,大多数查询都包含该字段
2. 分布均匀,避免数据倾斜
3. 相对稳定,不易变化
- 避免多层级拆分,控制分库分表的复杂度
2. 拆分粒度设计
- 分表数量:单表数据量控制在1000万以内,查询性能较好
- 分库数量:根据服务器资源和并发量确定,避免过多分库增加管理成本
- 预留扩容空间:初始设计时考虑未来3-5年的数据增长
3. 应用适配
- 避免使用SELECT *,只查询需要的字段
- 优化分页查询,分库分表环境下COUNT(*)和LIMIT分页代价高
- 避免复杂SQL,如多表JOIN、子查询等
- 批量操作尽量按分片键拆分,避免全库扫描
4. 监控与运维
- 监控各分库分表的容量、性能和负载均衡情况
- 定期检查数据分布,避免数据倾斜
- 建立完善的备份和恢复机制
- 记录分库分表的元数据信息,便于管理
5. 循序渐进
- 不是所有表都需要分库分表,只拆分数据量大或访问频繁的表
- 可以先单库分表,再逐步过渡到分库分表
- 小步迭代,每次拆分后进行充分测试
6. 工具选型
- 中小规模应用:可考虑客户端分片或轻量级中间件
- 大规模复杂应用:建议使用成熟的中间件如ShardingSphere
- 云环境:优先考虑云厂商提供的分布式数据库服务
六、分库分表示例场景
1. 电商订单系统
① 垂直拆分:
- 订单库(order_db):存储订单基本信息
- 订单明细表(order_item_db):存储订单商品明细
- 订单支付库(order_payment_db):存储支付相关信息
② 水平拆分:
订单表按用户ID哈希分8个库,每个库再按时间分12个表:
- order_db_0 至 order_db_7(8个分库)
- 每个分库包含 orders_202301 至 orders_202312(12个分表)
③ 分片键选择:
- 分库键:user_id(保证同一用户的订单在同一库)
- 分表键:create_time(按月份拆分)
2. 社交平台用户系统
① 垂直拆分:
- 用户基本信息库(user_basic_db)
- 用户资料库(user_profile_db)
- 用户关系库(user_relation_db):关注、粉丝等关系
② 水平拆分:
用户表按用户ID范围分4个库,每个库分16个表:
- user_basic_db_0 至 user_basic_db_3
- 每个库包含 user_00 至 user_15
③ 分片键选择:
- 分库分表键:user_id(便于按用户ID直接定位)
1696

被折叠的 条评论
为什么被折叠?



