最近在电商项目中遇到了订单表数据量激增的问题,单表查询性能明显下降。经过技术调研,最终选择用ShardingSphere-JDBC实现MySQL的水平分片方案。这里分享下具体的实现过程和踩坑经验。
水平分片的核心思想是将一个大表拆分成多个小表,分散到不同数据库节点上。比如我们把订单表t_order拆分成t_order0和t_order1,分别放在两个数据库中。这样每个表的数据量减少,查询性能自然提升。
首先需要准备分片用的数据库和表。我们创建了db_order数据库,并在其中创建了两个订单表:
sql复制CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
这里有个关键点:水平分片的id必须在业务层实现,不能依赖数据库的主键自增。因为不同分片表如果都自增,会导致ID冲突。我们后面会使用分布式ID生成策略。
在application.properties中配置基础信息:
properties复制# 应用名称
spring.application.name=sharding-jdbc-demo
# 开发环境设置
spring.profiles.active=dev
# 使用内存模式
spring.shardingsphere.mode.type=Memory
# 打印SQL日志
spring.shardingsphere.props.sql-show=true
这里开启了SQL日志打印,方便调试时分片路由是否正确。
我们配置了三个数据源:user、order0和order1:
properties复制# 配置真实数据源
spring.shardingsphere.datasource.names=user,order0,order1
# user数据源
spring.shardingsphere.datasource.user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.user.jdbc-url=jdbc:mysql://172.18.8.229:3307/db_user
spring.shardingsphere.datasource.user.username=root
spring.shardingsphere.datasource.user.password=123456
# order0数据源
spring.shardingsphere.datasource.order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.order0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.order0.jdbc-url=jdbc:mysql://localhost:3306/db_order
spring.shardingsphere.datasource.order0.username=root
spring.shardingsphere.datasource.order0.password=root
# order1数据源
spring.shardingsphere.datasource.order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.order1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.order1.jdbc-url=jdbc:mysql://172.18.8.229:3307/db_order
spring.shardingsphere.datasource.order1.username=root
spring.shardingsphere.datasource.order1.password=123456
定义逻辑表t_order对应的实际数据节点:
properties复制spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=order0.t_order0,order0.t_order1,order1.t_order0,order1.t_order1
这个配置表示t_order逻辑表实际存储在四个物理表中:order0库的t_order0和t_order1,以及order1库的t_order0和t_order1。
上面的配置比较冗长,可以使用行表达式简化:
properties复制spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=order$->{0..1}.t_order$->{0..1}
$->{0..1}表示从0到1的范围,这样配置更加简洁。
我们希望同一个用户的订单数据存储在同一个库中,这样可以提高查询效率。这里根据user_id的奇偶性决定数据路由:
properties复制# 分库策略
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid
# 行表达式分片算法
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=order$->{user_id % 2}
这个配置表示:
除了分库,我们还可以在同一个库内对表进行分片。比如根据订单ID的哈希值决定存储在哪个表:
properties复制# 分表策略
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=id
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_mod
# 取模分片算法
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2
这个配置表示:
前面提到不能使用数据库自增ID,我们需要在业务层生成分布式ID。这里使用MyBatis-Plus的ASSIGN_ID策略:
java复制@TableId(type = IdType.ASSIGN_ID) // 分布式ID
private Long id;
MyBatis-Plus默认使用雪花算法生成分布式ID,可以保证全局唯一。
编写测试方法插入数据:
java复制@Test
public void testInsertOrderDatabaseStrategy(){
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ORDER_" + i);
order.setUserId(i + 1);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}
执行后查看数据分布:
ShardingSphere-JDBC支持多种查询场景:
分片键选择:应该选择查询频繁且值分布均匀的字段,如user_id。避免选择值分布不均匀的字段,会导致数据倾斜。
分布式事务:跨分片的更新操作需要考虑分布式事务。ShardingSphere支持XA和柔性事务。
JOIN查询:尽量避免跨分片的JOIN查询,性能较差。可以通过冗余字段或业务层组装数据。
分布式ID:确保ID生成策略在分布式环境下唯一,推荐雪花算法。
SQL限制:某些复杂SQL可能不支持,如子查询、函数等。需要测试验证。
扩容考虑:设计分片规则时要考虑未来扩容,避免数据迁移困难。
索引设计:每个分片表都需要单独建立索引,与单表设计类似。
分片数量:不宜过多,一般建议2-8个分片。过多会导致连接数增加。
连接池配置:每个数据源都需要配置合适的连接池大小。
监控:监控各个分片的负载情况,及时发现数据倾斜问题。
冷热分离:可以将历史数据归档到单独的分片,提高热点数据查询性能。
通过合理的设计和配置,ShardingSphere-JDBC可以很好地解决单表数据量过大导致的性能问题。在实际项目中,建议先在小规模数据上测试验证,确保分片策略满足业务需求后再上线。