分片算法
约 6913 字大约 23 分钟
2025-10-08
基础案例
本案例中关键组件的版本:
| 关键组件 | 版本信息 |
|---|---|
| SpringBoot | 3.2.4 |
| SpringCloud | 2023.0.1 |
| ShardingSphere-JDBC | 5.5.2 |
| jdk | 17 |
| Mybatis-Plus | 3.5.12 |
引入依赖
sharding-jdbc的依赖有两种引入方式,第一种:
- ⛔️基于spring-boot-starter的方式引入
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>这个依赖已经停止更新了,目前最新的版本就是5.2.1,于2022年10月8日发布。
- ⭐️ 基于shardingsphere-jdbc的方式来引入
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.2</version>
</dependency>其实两种集成方式区别只是配置方式的区别。本案例的集成方式为shardingsphere-jdbc的方式。
增加相关配置信息
主要的配置分为:applicatio.yml(SpringBoot服务的默认配置)和sharding-jdbc.yml(shardingsphere-jdbc的配置)
application.yml
application.yml中主要是要进行数据源的配置,因为shardingsphere-jdbc是通过代理数据源的方式来进行的,所以对于数据源的配置需要关联上shardingsphere-jdbc。
spring:
datasource:
# 使用的数据源的驱动类的名称
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
# 配置shardingsphere-jdbc配置文件的路径为classpath:sharding-jdbc.yml
url: jdbc:shardingsphere:classpath:sharding-jdbc.yml这里主要的配置就是要:
- 指定数据源的类型为ShardingSphereDriver类型,这样才能代理我们默认的数据源。
- 指定ShardingSphere-JDBC的配置文件的位置,里面用于配置具体的数据源信息、分库分表的配置信息。
sharding-jdbc.yml
sharding-jdbc.yml是在application.yml中指定的shardingsphere-jdbc的配置文件(注意:文件名要能对应上)。
dataSources:
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
databaseName: study
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://10.8.97.111:3306/study?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
username: root
password: RootCC123@
rules:
- !SHARDING
tables:
t_order: # 逻辑表的表名
actualDataNodes: ds_1.t_order_${0..1} # 实际数据节点,使用占位符表示
tableStrategy: # 分表规则
standard: # 分片策略:standard、complex、hint、none四种
shardingColumn: order_id # 分片键
shardingAlgorithmName: t_order_sharding # 分片算法
keyGenerateStrategy: # 主键生成策略
column: order_id # 主键
keyGeneratorName: snowflake # 主键生成算法
shardingAlgorithms: # 分片算法
t_order_sharding:
type: INLINE # 行表达式分片算法
props:
algorithm-expression: t_order_${order_id % 2} # 使用行表达式(取模算法)
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
props:
sql-show: true上述配置文件中具体的表达的内容就是:
对于t_order表进行分表,分表的策略是通过order_id % 2的结果来决定的,在分表之后遇到批量插入的场景为了保证分表的order_id是全局唯一的(在其它的分表中也不能重复),采取了snowflake算法来生成分布式Id。
基础的CRUD代码构建
这里主要贴出torder0的建表语句:
CREATE TABLE t_order_0(
order_id BIGINT COMMENT '订单ID',
gmt_create DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
gmt_modify DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
price DECIMAL(10,2) COMMENT '订单金额',
user_id BIGINT COMMENT '用户ID',
PRIMARY KEY (order_id),
INDEX uid_idx(user_id) COMMENT '用户ID索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci至于具体的CRUD的代码这里就不放出类,引入了Mybatis-Plus之后,只需要简单的通过BaseMapper或者IService的方式来接入就好了。
需要注意的是在插入的时候分片键的值不能为空。如果分片键的值为空,shardingsphere-jdbc会对其进行补列,补列的值生成方式由keyGenerators来指定。
执行单元测试
批量插入测试
@Test
public void insertTest() {
for (int i = 0; i < 10; i++) {
OrderEntity order = new OrderEntity();
order.setPrice(new BigDecimal("100"));
order.setUserId(1000L + i);
orderMapper.insert(order);
}
}执行上面的SQL之后,可以在控制台看到的Logic SQL和Actual SQL的执行情况:
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1000, 1183355584607924224]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1001, 1183355584817639425]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1002, 1183355584851193856]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1003, 1183355584884748289]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1004, 1183355584909914112]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1005, 1183355584935079937]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1006, 1183355584964440064]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1007, 1183355584993800193]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1008, 1183355585023160320]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1009, 1183355585048326145]仔细观察上面的日志,其实就两点:
- 补列了,我们并没有主动设置分片键的值,但是shardingsphere-jdbc对其进行了补列;
- 插入的时候根据分片键计算了之后得到了每条记录需要插入到哪个真实表;(按照我们的分片规则,奇数插入到了的t_order_1表,偶数插入到台t_order_2表)
精确查询测试
精确查询分为两种:=和in,他们的逻辑是有区别的。
in操作
@Test
public void precisionTest(){
List<OrderEntity> orderEntities = orderMapper.selectList(
new LambdaQueryWrapper<OrderEntity>()
.in(OrderEntity::getOrderId, List.of(1183051788849754112L,1183355584993800193L,1183355584935079937L))
);
orderEntities.forEach(System.out::println);
}此时Logic SQL与Actual SQL之间的关系为:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id IN (?,?,?))
Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id IN (?,?,?)) UNION ALL SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (order_id IN (?,?,?)) ::: [1183051788849754112, 1183355584993800193, 1183355584935079937, 1183051788849754112, 1183355584993800193, 1183355584935079937]
在in操作中的条件下,它采取的方式是union关键字来直接聚合两个表中的数据。
=操作
=操作相对于in操作会更好理解,它可以直接根据分片键的值和分片算法来计算出当前分片键会在哪个真实表中,直接去对应的真实表中查询数据即可。
@Test
public void precisionTest() {
OrderEntity orderEntity = orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>().eq(OrderEntity::getOrderId, 1183051788849754112L));
System.out.println(orderEntity);
}此时Logic SQL 与 Actual SQL之间的关系为:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id = ?)
Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id = ?) ::: [1183051788849754112]不带分片键的查询
当我们查询不带分片键的时候, 它会扫描所有的表查询出符合条件的数据。这一点其实很好理解,不带分片键的时候,shardingsphere无法推断出数据所在的分片,只能扫描所有的数据分片。
@Test
public void selectTest() {
List<OrderEntity> orderEntities = orderMapper.selectList(new LambdaQueryWrapper<OrderEntity>().eq(OrderEntity::getUserId, 1000L));
orderEntities.forEach(System.out::println);
}此时Logic SQL 与 Actual SQL之间的关系为:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (user_id = ?)
: Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (user_id = ?) UNION ALL SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (user_id = ?) ::: [1000, 1000]在分库分表的场景下,无论是单分片键还是多分片键的查询,都需要带上分片键,否则走全表扫描的逻辑,查询的效率将会是极低的
基于范围的查询
如果我们要基于分片键的范围来查询,它也是会扫描所有的表。这是因为,在需要的范围内是不知道存在哪些键的,只能走全表扫描的方式来获取每个数据分片中符合条件的数据,然后再进行归并汇总。
基于INLINE的分片策略的时候是不支持分片键的分为查询的 ,解决的办法就是修改为STANDARD分片策略。
需要修改分片算法的配置:
shardingAlgorithms: # 分片算法
t_order_sharding:
type: CLASS_BASED # 自定义算法实现支持范围查询的分片算法
props:
# 使用CLASS_BASED模式的时strategy属性值必须设置
strategy: standard
algorithmClassName: com.hikvision.ifpd.sharding.StandardRangeShardingAlgorithm # 自定义分片算法类名使用CLASS_BASED(自定义算法)的模式来完成范围查询的支持。
@Slf4j
public class StandardRangeShardingAlgorithm implements StandardShardingAlgorithm<Long> {
/**
* @param collection 所有可用的分片值
* @param preciseShardingValue 精确查询的值
* @description: 如果是精确查询就会执行这个方法来获取分片的结果
**/
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
log.info("==========>>>>>>>>>> 可用分片的集合:{} 精确查询的值:{}", collection, preciseShardingValue);
// 根据分片键对2进行取模的结果来选择分片(对2取模的结果一定是0或1,所以直接强转就可以了)
int index = (int) (preciseShardingValue.getValue() % 2L);
return new ArrayList<String>(collection).get(index);
}
/**
* @param collection 所有可用的分片
* @param rangeShardingValue 范围查询的值
* @description: 如果是范围查询就会执行这个方法来获取分片的结果
**/
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
log.info("==========>>>>>>>>>> 可用分片的集合:{} 范围查询的值:[{},{}]",
collection, rangeShardingValue.getValueRange().lowerEndpoint(), rangeShardingValue.getValueRange().upperEndpoint());
return collection;
}
}测试范围查询
@Test
public void rangeTest() {
List<OrderEntity> orderEntities = orderMapper.selectList(new LambdaQueryWrapper<OrderEntity>().between(OrderEntity::getOrderId, 1000L, 2000L));
orderEntities.forEach(System.out::println);
}此时Logic SQL和Actual SQL之间的关系为:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id BETWEEN ? AND ?)
Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id BETWEEN ? AND ?) UNION ALL SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (order_id BETWEEN ? AND ?) ::: [1000, 2000, 1000, 2000]测试精确查询
@Test
public void precisionTest() {
OrderEntity orderEntity = orderMapper.selectOne(new LambdaQueryWrapper<OrderEntity>().eq(OrderEntity::getOrderId, 1183051788849754112L));
System.out.println(orderEntity);
}此时Logic SQL 和 Actual SQL之间的关系:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id = ?)
Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id = ?) ::: [1183051788849754112]数据分片
按照上面的案例,我们简单的实现了基于单库下的分表操作。在这种场景下,每个真实表其实就可以认为是一个数据分片。同理,在分库分表的场景下,每一个库中的每一个表其实就是一个数据分片。
针对上面分表逻辑,在后期数据不断增加的时候还需要进行分库,就需要对上面的案例进行改造。改造的工作量也是比较简单的(如果需要兼容旧的数据的话,就是比较的复杂的)。
修改之后的sharding-jdbc的配置信息:
dataSources:
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
databaseName: study
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://10.8.97.111:3306/study?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
username: root
password: RootCC123@
ds_2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
databaseName: study
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://10.8.97.111:3306/study_1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&useSSL=false
username: root
password: RootCC123@
rules:
- !SHARDING
tables:
t_order: # 逻辑表的表名
actualDataNodes: ds_${1..2}.t_order_${0..1} # 实际数据节点,使用占位符表示
databaseStrategy: # 分库规则
standard: # 分片策略:standard、complex、hint、none四种
shardingAlgorithmName: t_order_db # 分库算法
tableStrategy: # 分表规则
standard: # 分片策略:standard、complex、hint、none四种
shardingColumn: order_id # 分片键
shardingAlgorithmName: t_order_sharding # 分片算法
keyGenerateStrategy: # 主键生成策略
column: order_id # 主键
keyGeneratorName: snowflake # 主键生成算法
shardingAlgorithms: # 分片算法
t_order_sharding:
type: CLASS_BASED # 自定义算法实现支持范围查询的分片算法
props:
strategy: standard
algorithmClassName: com.hikvision.ifpd.sharding.StandardRangeShardingAlgorithm # 自定义分片算法类名
t_order_db:
type: INLINE # 通过行表达式分片算法来支持分库
props:
algorithm-expression: ds_${order_id % 2 + 1}
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
props:
sql-show: true
主要变化的内容有:
- 添加新的数据源信息(一个数据源就是一个数据库)
- 添加数据库的分片规则
上述配置文件的主要内容,t_order这个逻辑表:
- 分库规则:根据orderid来进行分库,一共有两个库study和study1
- 分表规则:每个库里面都有两张表torder0和torder1,也是根据order_id对2取模运算的结果来决定数据的所属分片;
其实简单来讲就是:如果order_id是奇数,就存储在ds_2(study_1数据库)数据源的t_order_1表中;如果order_id是偶数,就存储在ds_1(study数据库)数据源的t_order_0表中。
其实上面的分片策略是不合理的,会造成空间的冗余的。在实际使用的时候分库的策略和分片的策略是不相同的。可以是单个分片键的不同分法,例如分库按照orderid的范围来分,分表按照orderid的取模算法来分。
分库分表案例测试
批量插入测试
具体的执行批量插入的代码跟分表案例中是一样的,这里就不贴出来了,直接来看Logic SQL和Actual SQL之间的关系:
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1000, 1183433954947477504]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_2 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1001, 1183433955178164225]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1002, 1183433955203330048]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_2 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1003, 1183433955232690177]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1004, 1183433955257856000]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_2 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1005, 1183433955287216129]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1006, 1183433955308187648]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_2 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1007, 1183433955341742081]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1008, 1183433955366907904]
Logic SQL: INSERT INTO t_order ( price, user_id ) VALUES ( ?, ? )
Actual SQL: ds_2 ::: INSERT INTO t_order_1 ( price, user_id , order_id) VALUES (?, ?, ?) ::: [100, 1009, 1183433955396268033]根据这个SQL也是很明显的看出来,就是按照我们上面分析的规则来进行存储的。
精确查询测试
=操作
这里就不过多赘述了,直接来看Logic SQL与Actual SQL之间的关系:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id = ?)
Actual SQL: ds_2 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (order_id = ?) ::: [1183433955396268033]可以看到对于orderid = 1183433955396268033 的条件,它直接查询的就是ds2数据源中的torder1表。
in操作
Logic SQL 与 Actual SQL之间的关系:
Logic SQL: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order WHERE (order_id IN (?,?,?))
Actual SQL: ds_1 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id IN (?,?,?)) UNION ALL SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (order_id IN (?,?,?)) ::: [1183355584607924224, 1183433955366907904, 1183433955341742081, 1183355584607924224, 1183433955366907904, 1183433955341742081]
Actual SQL: ds_2 ::: SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_0 WHERE (order_id IN (?,?,?)) UNION ALL SELECT order_id,gmt_create,gmt_modify,price,user_id FROM t_order_1 WHERE (order_id IN (?,?,?)) ::: [1183355584607924224, 1183433955366907904, 1183433955341742081, 1183355584607924224, 1183433955366907904, 1183433955341742081]可以看到与分表是一样的,对于in类型的精确查询都是通过union关键字来实现的。
其实这里我在shardingspher-jdbc的4.x文档中看到了有关IN操作批量查询的都有花,但是在shardingsphere-jdbc的5.x文档中还是没有对应逻辑出现,所以在做in操作的时候需要考虑到性能的消耗,手动进行分批,保证In的条件中的不跨过多的分片。
范围查询
对于范围的查询,使用INLINE分片算法是不支持的(具体的原因参考后面关于分片策略的分析)。同样需要修改为CLASS_BASED的方式来自定义一个分片算法来支持范围查询,或者使用其他的支持范围查询的分片算法。
分片策略
分片策略是由分片键和分片算法来构成的。
分片算法
分片算法按照形式分为两种:自动化分片算法和自定义分片算法。它们主要是对=、>=、<=、>、<、Between和in进行分片。
在当前版本下shardingsphere-jdbc提供的分片算法的类型如下图所示:

自动化分片算法
自动化分片算法是内置的一些分片算法,算是自定义分片算法的语法糖。它支持取模、哈希、范围、事件等常用分片算法的实现。
要使用自动化分片算法的配置如下:
rules:
- !SHARDING
autoTables: # 分片规则这里必须使用autoTables才可以
t_order: # 逻辑表的表名
actualDataSources: ds_1 # 数据源名称
shardingStrategy: # 分表规则
standard: # 分片策略:standard、complex、hint、none四种
shardingColumn: order_id # 分片键
shardingAlgorithmName: t_order_sharding # 分片算法需要注意的是使用自动化分片算法的前缀是autoTables,而不是tables。
MOD算法
对应的实现类为:org.apache.shardingsphere.sharding.algorithm.sharding.mod.ModShardingAlgorithm
取模算法是最常用的算法,它的配置方式为:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: MOD # 分批算法的类型
props:
sharding-count: 2 # 分片的数量
start-offset: 0 # 分片键截取的起始下标
stop-offset: 2 # 分片键截取的结束下标
zero-padding: fasle # 是否需要进行0填充分片计算结果这个是基础的取模算法,就是使用分片键直接对分片数量取模。
MOD算法的属性介绍:
sharding-count:分片的数量,这个好理解,就是要将表分成多少个数据分片;
start-offset:从分片键中进行截取,开始下标的值;
stop-offset:从分片键中进行截取,结束下标的值;
start-offset和stop-offset联合使用,可以从分片键中截取部分的内容来进行分片。例如,order_id的值为
abc_122,就可以设置start-offset和stop-offset的值截取122来进行取模运算。zero-padding:是否需要使用
0来进行填充例如,分片的表有10中,
t_order_00 ~ t_order_09,此时就可以使用0对取模的值进行填充;
HASH_MOD算法org.apache.shardingsphere.sharding.algorithm.sharding.mod.HashModShardingAlgorithm
它的配置方式为:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: HASH_MOD # 分批算法的类型
props:
sharding-count: 2 # 分片的数量在它的实现类中具体的分片结果的计算方法是Math.abs((long) shardingValue.hashCode()),就是先计算分片键的hashcode的值,然后对hashcode的值对分片的数量进行取模。
BOUNDARY_RANGE算法
具体实现类:org.apache.shardingsphere.sharding.algorithm.sharding.range.BoundaryBasedRangeShardingAlgorithm
配置的方式:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: BOUNDARY_RANGE # 分批算法的类型
props:
sharding-ranges: 0,100,200,300,400 # 分片的区间基于范围边界的分片算法,上面配置的含义意思就是:t_order_0 ->|0| t_order_1 ->|100|-> t_order_2 ->|200|-> t_order_3 ->|300|-> t_order_4 ->|400|-> t_order_5
VOLUME_RANGE算法
具体实现类:org.apache.shardingsphere.sharding.algorithm.sharding.range.VolumeBasedRangeShardingAlgorithm
配置的方式:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: VOLUME_RANGE # 分批算法的类型
props:
sharding-volume: 20 # 区间的步长
range-lower: 100 # 范围的上限
range_upper: 200 # 范围的下限基于区间的分片算法,上面的配置会将分片键分为成7个区间:{0=(-∞..100), 1=[100..120), 2=[120..140), 3=[140..160), 4=[160..180), 5=[180..200), 6=[200..+∞)},然后根据分片键的值落在那个区间,对应区间的下标就是的分片的结果。
AUTO_INTERVAL算法
具体实现类:org.apache.shardingsphere.sharding.algorithm.sharding.datetime.AutoIntervalShardingAlgorithm
配置的方式:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: AUTO_INTERVAL # 分批算法的类型
props:
datetime-lower: 2025-10-11 00:00:00 # 区间的步长
datetime-upper: 2025-10-11 23:59:59 # 范围的上限
sharding-seconds: 7200 # 时间段的大小(单位:秒)基于时间的分片算法,上面的配置的意思就是其实跟VOLUME_RANGE算法类似,只不过是的要传入的是时间而已,它的分片结果如下所示:

但是其实还有点区别的,我在5.5版本中的代码中他通过计算得到的分片数量是5个,如果是VOLUMN_RANGE的算法它会是6个。
自定义分片算法
使用自定义分片算法的时候需要自己来配置逻辑表与真实表之间的映射关系,而且它需要使用的前缀是tables而不是autoTables。
rules:
- !SHARDING
tables:
t_order: # 逻辑表的表名
actualDataNodes: ds_1.t_order_${0..1}
shardingStrategy: # 分表规则
standard: # 分片策略:standard、complex、hint、none四种
shardingColumn: order_id # 分片键
shardingAlgorithmName: t_order_sharding # 分片算法数据分片与真实表之间的映射关系,可以通过grovy表达式来指定。下面简单介绍几种简单的配置方式,可以应对大部分的场景:
- 连续分表
ds_1.t_order_${0..3}
# 对应的数据源为ds_1,表为:
# t_order_0
# t_order_1
# t_order_2
# t_order_3- 离散分表
ds_1.t_order_${1,3,5,7}
# 对应数据源为ds_1,表为:
# t_order_1
# t_order_3
# t_order_5
# t_order_7上述的规则同样使用与分库,当分库和分表同时使用的时候,实际的数据分片是它们之间的笛卡尔积
标准分片算法
标准分片算法主要是负责单一键作为分片键的=、IN、BETWEEN、>、<、>=和<=的场景。
它对应的接口为:org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm,其中常
除开自动分片算法之后,可以用于自定义分片算法的只有ClassBasedShardingAlgorithm、InlineShardingAlgorithm和IntervalShardingAlgorithm三种。
InlineShardingAlgorithm(行表达式分片算法)
基于行表达式来进行分片,具体的使用方式可以参考上面的基础案例;
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: INLINE # 修改为 INLINE 类型
props:
algorithm-expression: t_order_{order_id % 2} # 用 order_id对2进行取模的结果作为分片的结果
allow-range-query-with-inline-sharding: false # 是否支持范围查询这里的配置需要额外注意就是allow-range-query-with-inline-sharding这个配置项,它默认是不支持范围查询的。如果我们希望它支持范围查询,只需要将其配置为true即可。
ClassBasedShardingAlgorithm(自定义分片算法)
自定义实现分片算法,具体的使用方式可以参考上面的基础案例(范围查询)。
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: CLASS_BASED # 自定义分片算法
props:
strategy: STANDARD # 仅支持 STANDARD、COMPLEX 和 HINT
algorithmClassName: com.hikvision.ifpd.sharding.StandardRangeShardingAlgorithm属性介绍:
- strategy —— 表示自定义分片算法的类型
- STANDARD表示标准分片算法,自定义类需要实现
org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm接口; - Complex表示复合分片算法,自定义类需要实现
org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm接口; - HINT表示HINT分片算法,自定义类需要实现
org.apache.shardingsphere.sharding.api.sharding.hint.HintShardingAlgorithm - algorithmClassName —— 表示自定义类的全限定类名。
INTERVAL算法(时间范围分片算法)
具体的实现类:org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm
配置的方式:
shardingAlgorithms:
t_order_sharding: # 修改算法名称
type: INTERVAL # 分批算法的类型
props:
datetime-pattern: yyyy-MM-dd HH:mm:ss # 时间的格式
datetime-lower: 2025-10-11 00:00:00 # 起始时间
datetime-upper: 2025-10-11 23:59:59 # 结束时间
sharding-suffix-pattern: HH # 分片后缀的模式
datetime-interval-amount: 6 # 每个分片的大小
datetime-interval-unit: Hours # 时间间隔的单位基于时间的分片算法,上面的配置的意思就是其实跟VOLUME_RANGE算法类似,只不过是的要传入的是时间而已,它的分片结果如下所示:

与AUTO_INTERVAL不同的是,超过时间范围的INTERVAL算法都会返回null,找不到对应的分片。
属性介绍:
- datetime-pattern:分片键的时间格式,要求遵循LDML标准;
- datetime-lower:时间分片的范围的下限,值要遵循按照datetime-pattern配置的格式;
- datetime-upper:时间分片的范围的上限,值要遵循按照datetime-pattern配置的格式;
- sharding-suffix-pattern:根据分片键计算出对应的区间后,取区间的下限作为输出字符串,然后根据配置的格式来取出区间下限中的具体内容作为表的后缀。例如:2025-10-11 07:00:00 经过分片计算后会落在【2025-10-11 06:00:00,2025-10-11 12:00:00】这个区间,然后分片的结果就是 2025-10-11 06:00:00,我们配置的是HH格式,就是小时部分,所以最终分片的结果就是torder06。
- datetime-interval-amount:每个分片中时间段的大小,与datetime-interval-unit搭配使用;
- datetime-interval-unit:分片中时间段大小的单位,与datetime-interval-unit搭配使用。可选值可以参考:
java.time.temporal.ChronoUnit中的枚举类型。
复合分片算法
复合分片算法用于处理多个分片键进行分片的场景,包含多个分片键的逻辑较为复杂,大部分场景下都是需要自己开发的。复合分片算法对应的接口为:org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm。
其实复合分片算法比较简单只有ClassBasedShardingAlgorithm和ComplexInlineShardingAlgorithm两种,至于ClassBasedShardingAlgorithm这里就不赘述了,基本上的使用方式都是相同的。
ComplexInlineShardingAlgorithm(基于行表达式的复合分片算法)
复合分片算法通常被用来适配多个分片键的算法,例如,对于t_order表,我们需要根据年份的来进行分表,但是担心每个年份中的数据太多,仍然需要使用order_id来进行进一步分表。如下所谓为复合分片策略的配置方式:
rules:
- !SHARDING
tables:
t_order:
# 一共是四张表:t_order_0_2024/t_order_0_2025/t_order_1_2024/t_order_1_2025
actualDataNodes: ds_1.t_order_$->{0..1}_$->{2024..2025}
tableStrategy:
complex: # 注意这里与标准分片策略之间的差别
shardingColumns: order_id,gmt_create # 注意这里与标准分片策略之间的差别
shardingAlgorithmName: t_order_complex
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
keyGenerators:
snowflake:
type: SNOWFLAKE
shardingAlgorithms:
t_order_complex:
type: COMPLEX_INLINE
props:
algorithm-expression: t_order_$->{order_id % 2}_$->{gmt_create.year}
sharding-columns: order_id,gmt_create同样的是使用COMPLEX_INLINE算法的时候,它仍然是默认不支持范围查询的,需要我们通过allow-range-query-with-inline-sharding参数来开启。开启之后是支持范围查询的,但是它是不走分片键的,默认是扫描所有的分片。
注意
不建议在使用INLINE、COMPLEX_INLINE或HINT_INLINE的时候开启范围查询,在数据量较大的情况下,它的性能是非常低的
具体的测试步骤可以参考基础案例中的批量插入。但是需要注意的是:t_order_$->{order_id % 2}_$->{gmt_create.year}中gmt_create字段的了类型必须是有getYear()方法的,它是shardingsphere集成的表达式,方便获取对象的year属性。
按照我们上面建立的表的名称,我们希望最后的后缀是自然年份,但是我们使用gmt_create的是Date类型的时候,它的getYear()方法的主要逻辑是2025-1970=125,这会导致它计算出来的表名为t_order_125。
Date类型是比较老的时间日期类,在JdK8中推出了LocalDateTime类型,建议后面的使用都可以尽可能使用LocalDateTime来表示日期。
HINT分片算法
HINT分片算法的意思就是我们插入的时候手动指定这个SQL要放入到哪个分片中。
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_1.t_order_$->{0..1}_2024
tableStrategy:
hint: # 配置为HINT分片策略
shardingAlgorithmName: t_order_hint # HINT分片策略对应的分片算法
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
keyGenerators:
snowflake:
type: SNOWFLAKE
shardingAlgorithms:
t_order_hint:
type: HINT_INLINE
props:
algorithm-expression: t_order_${value % 2}_2024 # HINT分片策略的分片表达式,${value}为HINT分片键的值假如现在需要对它t_order_0_2024和t_order_1_2024表来进行数据存储。然后配置HINT算法的表达式,其中value指的是进行分片的值。
HINT使用的不是的分片键,而是在执行SQL之前需要手动指定进行分片的值
我们在正式使用的时候需要使用HintManager来完成的:
@Test
public void insertOrder() {
HintManager hintManager = HintManager.getInstance();
for (int i = 0; i < 10; i++) {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserId(i % 2L);
orderEntity.setGmtCreate(new Date());
orderEntity.setPrice(new BigDecimal("100.00"));
hintManager.clearShardingValues();
hintManager.addTableShardingValue("t_order", i);
log.info("==========>>>>>>>>>> 插入数据:{}", JSONUtil.toJsonStr(orderEntity));
orderMapper.insert(orderEntity);
}
}HintManager具体的是通过TransmittableThreadLocal来进行存储的,所以在一个线程中需要对同一个逻辑表设置多个分片值的时候,需要先清除当前线程内的分片值再设置对应的分片值。
如上面的代码执行之后的Logic SQL 和 Actual SQL之间的关系为:
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:30.205, 100.00, 0, 1183922551806492672]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.695, 100.00, 1, 1183922552347557889]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.7, 100.00, 0, 1183922552364335104]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.703, 100.00, 1, 1183922552372723713]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.705, 100.00, 0, 1183922552381112320]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.706, 100.00, 1, 1183922552389500929]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.708, 100.00, 0, 1183922552397889536]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.71, 100.00, 1, 1183922552406278145]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_0_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.712, 100.00, 0, 1183922552414666752]
Logic SQL: INSERT INTO t_order ( gmt_create, price, user_id ) VALUES ( ?, ?, ? )
Actual SQL: ds_1 ::: INSERT INTO t_order_1_2024 ( gmt_create, price, user_id , order_id) VALUES (?, ?, ?, ?) ::: [2025-10-12 00:05:32.714, 100.00, 1, 1183922552418861057]