分库分表是为了应对海量数据或高并发场景的一种数据库架构优化技术,其核心思想是通过水平和垂直切分的方式,将数据分散到多个库或表中,提升系统的读写性能和扩展性。 以下是分库分表的相关概念、策略和实现细节:
分库分表的两种主要策略
-
水平分片(Sharding)
- 特点:将同一张表的数据按某种规则拆分到多个库或表中。
- 适用场景:单表数据量过大,查询效率降低。
-
实现方式:
- 按字段取模(如:
user_id % N
)。 - 按范围(如:
1-100万用户放入库1,100万-200万放入库2
)。 - 按哈希分布。
- 按字段取模(如:
-
垂直拆分(Vertical Partitioning)
- 特点:按照业务模块将不同的表分布到不同的库中。
- 适用场景:数据库中表过多或存在跨业务查询。
-
实现方式:
- 不同的业务模块(如用户、订单)分配到不同的数据库。
分库分表的核心问题及解决方案
-
路由问题
- 问题:如何根据分库分表规则定位具体的库或表。
-
解决方案:
- 自定义路由规则(如基于
user_id
的哈希算法)。 - 使用中间件(如 ShardingSphere、MyCAT)来自动路由。
- 自定义路由规则(如基于
-
事务问题
- 问题:分布式事务在多库间的操作会非常复杂。
-
解决方案:
- 两阶段提交(2PC):通过事务协调器保证分布式事务。
- TCC 模式(Try-Confirm-Cancel):需要业务逻辑支持。
- BASE 理论:允许弱一致性,最终达到一致性。
-
跨库/跨表查询问题
- 问题:分库分表后,涉及多个库或表的数据查询会变得复杂。
-
解决方案:
- 聚合查询:通过代码逻辑在应用层进行数据整合。
- 中间件支持:如 ShardingSphere 提供 SQL 聚合查询功能。
-
主键冲突问题
- 问题:分库分表后,各表可能会产生主键冲突。
-
解决方案:
- 全局唯一 ID:使用分布式 ID 生成器(如 Snowflake 算法)。
- 字段前缀:通过库/表编号给主键加前缀。
常见中间件解决方案
-
ShardingSphere
- 特点:支持分库分表、读写分离、分布式事务。
- 使用方式:基于 Spring Boot 直接配置。
- 示例配置:
spring: shardingsphere: datasource: names: ds0, ds1 ds0: url: jdbc:mysql://localhost:3306/db0 username: root password: root ds1: url: jdbc:mysql://localhost:3306/db1 username: root password: root sharding: tables: user: actual-data-nodes: ds${0..1}.user_${0..1} table-strategy: inline: sharding-column: user_id algorithm-expression: user_${user_id % 2} database-strategy: inline: sharding-column: user_id algorithm-expression: ds${user_id % 2}
-
MyCAT
- 特点:基于代理模式,支持分库分表和读写分离。
- 缺点:需要额外维护 MyCAT 服务器。
-
Vitess
- 特点:由 YouTube 开发,支持 MySQL 的分库分表。
-
TDDL
- 特点:阿里巴巴的分库分表框架。
分库分表的实现示例
以用户表为例,将用户数据按user_id % 2
分到两个库,每个库中包含两张表。
数据库结构
- 库1:
db1
,包含表user_0
和user_1
- 库2:
db2
,包含表user_0
和user_1
自定义分库分表代码
public class ShardingRouter { private static final int DATABASE_COUNT = 2; // 数据库数量 private static final int TABLE_COUNT = 2; // 每个库中的表数量 /** * 根据 userId 获取数据库名称 */ public static String getDatabaseName(int userId) { int dbIndex = userId % DATABASE_COUNT; return "db" + (dbIndex + 1); // 数据库命名:db1, db2 } /** * 根据 userId 获取表名称 */ public static String getTableName(int userId) { int tableIndex = userId % TABLE_COUNT; return "user_" + tableIndex; // 表命名:user_0, user_1 } public static void main(String[] args) { int userId = 12345; String dbName = getDatabaseName(userId); String tableName = getTableName(userId); System.out.println("数据库:" + dbName + ",表:" + tableName); } }
分库分表适用场景
- 大型电商平台:订单、用户、商品等海量数据的存储与查询。
- 物流系统:包裹信息存储。
- 银行业务:交易流水数据。