赞
踩
官网: https://shardingsphere.apache.org/index_zh.html
官方文档:https://shardingsphere.apache.org/document/current/cn/overview/
-- ------------------------------ -- 用户表 -- ------------------------------ CREATE TABLE `t_user` ( `id` bigint(16) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(64) NOT NULL COMMENT '密码', `age` int(8) NOT NULL COMMENT '年龄', `salary` int(8) NOT NULL COMMENT '工资', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表'; -- ------------------------------ -- 用户表1 -- ------------------------------ CREATE TABLE `t_user_1` ( `id` bigint(16) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(64) NOT NULL COMMENT '密码', `age` int(8) NOT NULL COMMENT '年龄', `salary` int(8) NOT NULL COMMENT '工资', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表1'; -- ------------------------------ -- 用户表2 -- ------------------------------ CREATE TABLE `t_user_2` ( `id` bigint(16) NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(64) NOT NULL COMMENT '密码', `age` int(8) NOT NULL COMMENT '年龄', `salary` int(8) NOT NULL COMMENT '工资', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表2';
注意:数据库命名不能使用下划线,会报错:mydb_0 invalid
spring: shardingsphere: # 打印sql props: sql: show: true datasource: names: mydb-1,mydb-2 mydb-1: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/mydb-1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root # 数据源其他配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 #filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 mydb-2: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/mydb-2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root # 数据源其他配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 #filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
标准分片策略(StandardShardingStrategy): 只支持对
单个分片键
为依据的分库分表,并提供了两种分片算法:
PreciseShardingAlgorithm(精准分片)
:在使用标准分片策略时,精准分片算法时必须实现的算法,用于SQL含有 = 和 IN 的分片处理;
RangeShardingAlgorithm(范围分片)
:非必选的,用于处理含有 BETWEEN AND 的分片处理。注意: 一旦我们没配置范围分片算法,而SQL中又用到 BETWEEN AND 或者 LIKE 等,那么 SQL 将按全库、表路由的方式逐一执行,查询性能会很差。
使用方法:
使用四种分片策略的方式大致相同,都要实现相应的 ShardingAlgorithm
接口,并重写 doSharding() 方法,只是配置稍有不同,doSharding() 方法本身只是个空方法,需要我们自行处理分库、分表逻辑。
使用场景:
select * from t_user where age = 10 or age in (10,11,12);
yaml配置:
spring:
shardingsphere:
sharding:
# 默认数据源,未分片的表默认执行库
default-data-source-name: mydb-1
# 表策略配置
tables:
# t_user 是逻辑表
t_user:
databaseStrategy:
standard:
# 分片键
shardingColumn: age
# 精准分库算法
preciseAlgorithmClassName: com.demo.module.config.MyDBPreciseShardingAlgorithm
代码实现:
实现 PreciseShardingAlgorithm 接口,并重写 doSharding()。
public class MyDBPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> { @Override public String doSharding(Collection<String> databaseNames, PreciseShardingValue<Integer> shardingValue) { /** * databaseNames 所有分片库的集合 * shardingValue 为分片属性,其中 logicTableName 为逻辑表,columnName 分片键,value 为从 SQL 中解析出的分片键的值 */ for (String databaseName : databaseNames) { // 根据年龄判断,未成年 -> mydb-1, 已成年 -> mydb-2 String value = String.valueOf(shardingValue.getValue() < 18 ? 1 : 2); if (databaseName.endsWith(value)) { return databaseName; } } throw new IllegalArgumentException("分片失败,databaseNames:" + databaseNames); } }
yaml配置:
spring: shardingsphere: sharding: # 默认数据源,未分片的表默认执行库 default-data-source-name: mydb-1 # 表策略配置 tables: # t_user 是逻辑表 t_user: # 分表节点 可以理解为分表后的那些表 比如 t_user_1 ,t_user_2 actualDataNodes: mydb-1.t_user_$->{1..2} tableStrategy: standard: # 分片键 shardingColumn: age # 精准分表算法 preciseAlgorithmClassName: com.demo.module.config.MyTablePreciseShardingAlgorithm
代码实现:
实现 PreciseShardingAlgorithm 接口,并重写 doSharding()。
public class MyTablePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> { @Override public String doSharding(Collection<String> tableNames, PreciseShardingValue<Integer> shardingValue) { /** * tableNames 对应分片库中所有分片表的集合 * shardingValue 为分片属性,其中 logicTableName 为逻辑表,columnName 分片键,value 为从 SQL 中解析出来的分片键的值 */ for (String tableName : tableNames) { // 取模算法,分片键 % 表数量 + 1 String value = String.valueOf(shardingValue.getValue() % tableNames.size() + 1); if (tableName.endsWith(value)) { return tableName; } } throw new IllegalArgumentException("分片失败,tableNames:" + tableNames); } }
使用场景:
select * from t_user where age between 10 and 12;
yaml配置:
spring: shardingsphere: sharding: # 默认数据源,未分片的表默认执行库 default-data-source-name: mydb-1 # 表策略配置 tables: # t_user 是逻辑表 t_user: databaseStrategy: standard: # 分片键 shardingColumn: age # 精准分库算法 preciseAlgorithmClassName: com.demo.module.config.MyDBPreciseShardingAlgorithm # 范围分库算法 rangeAlgorithmClassName: com.demo.module.config.MyDBRangeShardingAlgorithm
代码实现:
实现 PreciseShardingAlgorithm 接口,并重写 doSharding()。
public class MyDBRangeShardingAlgorithm implements RangeShardingAlgorithm<Integer> { /** * 数据库分片符号 */ private final String DATABASE_SPLIT_SYMBOL = "-"; @Override public Collection<String> doSharding(Collection<String> databaseNames, RangeShardingValue<Integer> rangeShardingValue) { Set<String> result = new LinkedHashSet<>(); // between and 的起始值 Range<Integer> valueRange = rangeShardingValue.getValueRange(); boolean hasLowerBound = valueRange.hasLowerBound(); boolean hasUpperBound = valueRange.hasUpperBound(); // 计算最大值和最小值(未成年 -> mydb-1, 已成年 -> mydb-2) int lower; if (hasLowerBound) { lower = valueRange.lowerEndpoint() < 18 ? 1 : 2; } else { lower = getLowerEndpoint(databaseNames); } int upper; if (hasUpperBound) { upper = valueRange.upperEndpoint() < 18 ? 1 : 2; } else { upper = getUpperEndpoint(databaseNames); } // 循环范围计算分库逻辑 for (int i = lower; i <= upper; i++) { for (String databaseName : databaseNames) { String value = String.valueOf(i); if (databaseName.endsWith(value)) { result.add(databaseName); } } } return result; } // -------------------------------------------------------------------------------------------------------------- // 私有方法 // -------------------------------------------------------------------------------------------------------------- /** * 获取 最小分片值 * @param databaseNames 数据库名 * @return 最小分片值 */ private int getLowerEndpoint(Collection<String> databaseNames) { if (CollectionUtils.isNotEmpty(databaseNames)) { return databaseNames.stream().filter(o -> o != null && o.contains(DATABASE_SPLIT_SYMBOL)) .mapToInt(o -> { String[] splits = o.split(DATABASE_SPLIT_SYMBOL); return Integer.valueOf(splits[splits.length - 1]); }).min().orElse(-1); } return -1; } /** * 获取 最大分片值 * @param databaseNames 数据库名 * @return 最大分片值 */ private int getUpperEndpoint(Collection<String> databaseNames) { if (CollectionUtils.isNotEmpty(databaseNames)) { return databaseNames.stream().filter(o -> o != null && o.contains(DATABASE_SPLIT_SYMBOL)) .mapToInt(o -> { String[] splits = o.split(DATABASE_SPLIT_SYMBOL); return Integer.valueOf(splits[splits.length - 1]); }).max().orElse(-1); } return -1; } }
测试验证1-插入数据:
@Test
void saveTest() {
List<TUser> users = new ArrayList<>(3);
users.add(new TUser("ACGkaka_1", "123456", 10, 5000));
users.add(new TUser("ACGkaka_2", "123456", 15, 6000));
users.add(new TUser("ACGkaka_3", "123456", 18, 7000));
userService.saveBatch(users);
}
操作结果:
测试验证2-查询数据:
@Test
void listTest() {
QueryWrapper<TUser> wrapper = new QueryWrapper<>();
wrapper.ge("age", 15);
List<TUser> users = userService.list(wrapper);
System.out.println(">>>>>>>>>> 【Result】 <<<<<<<<<< ");
users.forEach(System.out::println);
}
操作结果:
使用场景:
select * from t_user where age=10 and username='ACGkaka_1';
yaml配置:
spring:
shardingsphere:
sharding:
# 表策略配置
tables:
# t_user 是逻辑表
t_user:
databaseStrategy:
standard:
# 分片键
shardingColumn: age
# 精准分库算法
preciseAlgorithmClassName: com.demo.module.config.MyDBPreciseShardingAlgorithm
# 范围分库算法
rangeAlgorithmClassName: com.demo.module.config.MyDBRangeShardingAlgorithm
代码实现:
public class MyDBComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<Integer> { /** * 复合分库 * * @param databaseNames 全部库名集合(用于筛选) * @param complexKeysShardingValue 复合分片值 * @return 需要用到的库名集合 */ @Override public Collection<String> doSharding(Collection<String> databaseNames, ComplexKeysShardingValue<Integer> complexKeysShardingValue) { // 精准分片-得到每个分片键对应的值 Collection<Integer> ageValues = this.getPreciseShardingValue(complexKeysShardingValue, "age"); Collection<Integer> salaryValues = this.getPreciseShardingValue(complexKeysShardingValue, "salary"); // 精准分片-根据两个分片键进行分库 List<String> ageSuffix = ageValues.stream().map(o -> o < 18 ? "1" : "2").collect(Collectors.toList()); List<String> salarySuffix = salaryValues.stream().map(o -> o < 5000 ? "1" : "2").collect(Collectors.toList()); List<String> shardingSuffix = getShardingSuffix(ageSuffix, salarySuffix); // 范围分片-得到每个分片键对应的值 Range<Integer> ageRange = this.getRangeShardingValue(complexKeysShardingValue, "age"); Range<Integer> salaryRange = this.getRangeShardingValue(complexKeysShardingValue, "salary"); // 范围分片-根据两个分片键进行分库 ageSuffix = getRangeSuffix(ageRange); salarySuffix = getRangeSuffix(salaryRange); shardingSuffix.addAll(getShardingSuffix(ageSuffix, salarySuffix)); return shardingSuffix.stream().distinct().collect(Collectors.toList()); } // -------------------------------------------------------------------------------------------------------------- // 私有方法 // -------------------------------------------------------------------------------------------------------------- /** * 获取 分库的范围 * @param range 分片值范围 * @return 分库范围 */ private List<String> getRangeSuffix(Range<Integer> range) { boolean isValid = range != null && (range.hasLowerBound() || range.hasUpperBound()); if (isValid) { List<String> rangeSuffix = new ArrayList<>(); int lowerEndpoint = range.hasLowerBound() ? range.lowerEndpoint() < 18 ? 1 : 2 : 1; int upperEndpoint = range.hasUpperBound() ? range.upperEndpoint() < 18 ? 1 : 2 : 2; for (int i = lowerEndpoint; i <= upperEndpoint; i++) { rangeSuffix.add(String.valueOf(i)); } return rangeSuffix; } return new ArrayList<>(0); } /** * 获取 库名集合 * @param ageSuffix age分片值 * @param salarySuffix salary分片值 * @return 库名集合 */ private List<String> getShardingSuffix(List<String> ageSuffix, List<String> salarySuffix) { List<String> dbNames = new ArrayList<>(); ageSuffix = ageSuffix == null ? new ArrayList<>(0) : ageSuffix; salarySuffix = salarySuffix == null ? new ArrayList<>(0) : salarySuffix; for (String age : ageSuffix) { for (String salary : salarySuffix) { dbNames.add(String.format("mydb-%s-%s", age, salary)); } } return dbNames.stream().distinct().collect(Collectors.toList()); } /** * 获取 分片键对应的值 * * @param shardingValue 复合分片值 * @param key 分片键 * @return 值 */ private Collection<Integer> getPreciseShardingValue(ComplexKeysShardingValue<Integer> shardingValue, final String key) { // 判断非空 if (StringUtils.isEmpty(key) || shardingValue == null) { return new ArrayList<>(0); } // 根据 分片键 取值 Collection<Integer> valueSet = new ArrayList<>(); Map<String, Collection<Integer>> columnNameAndShardingValueMap = shardingValue.getColumnNameAndShardingValuesMap(); Map<String, String> columnNameMap = columnNameAndShardingValueMap.keySet().stream().filter(Objects::nonNull) .collect(Collectors.toMap(String::toUpperCase, o -> o, (k1, k2) -> k1)); if (columnNameMap.containsKey(key.toUpperCase())) { String columnName = columnNameMap.get(key.toUpperCase()); valueSet.addAll(columnNameAndShardingValueMap.get(columnName)); } return valueSet; } /** * 获取 分片键对应的值 * * @param shardingValue 复合分片值 * @param key 分片键 * @return 值 */ private Range<Integer> getRangeShardingValue(ComplexKeysShardingValue<Integer> shardingValue, final String key) { // 判断非空 if (StringUtils.isEmpty(key) || shardingValue == null) { return null; } // 根据 分片键 取值 Map<String, Range<Integer>> columnNameAndRangeValuesMap = shardingValue.getColumnNameAndRangeValuesMap(); Map<String, String> columnNameMap = columnNameAndRangeValuesMap.keySet().stream().filter(Objects::nonNull) .collect(Collectors.toMap(String::toUpperCase, o -> o, (k1, k2) -> k1)); if (columnNameMap.containsKey(key.toUpperCase())) { String columnName = columnNameMap.get(key.toUpperCase()); return columnNameAndRangeValuesMap.get(columnName); } else { return null; } } }
测试验证1-插入数据:
@Test
void saveTest() {
List<TUser> users = new ArrayList<>(3);
users.add(new TUser("ACGkaka_1", "123456", 10, 3000));
users.add(new TUser("ACGkaka_2", "123456", 18, 4000));
users.add(new TUser("ACGkaka_3", "123456", 15, 6000));
users.add(new TUser("ACGkaka_4", "123456", 19, 7000));
userService.saveBatch(users);
}
操作结果:
测试验证2-查询数据:
@Test
void listTest() {
QueryWrapper<TUser> wrapper = new QueryWrapper<>();
wrapper.ge("age", 18);
wrapper.ge("salary", 5000);
List<TUser> users = userService.list(wrapper);
System.out.println(">>>>>>>>>> 【Result】 <<<<<<<<<< ");
users.forEach(System.out::println);
}
操作结果:
**行表达式分片策略(InlineShardingStrategy):**在配置中使用
Groovy 表达式
,提供对 SQL 语言中的 = 和 IN 的分片操作支持,它只支持单分片键。
使用场景:
适用于做简单的分片算法,无需自定义分片算法,省去了繁琐的代码开发,是四种分片策略中最为简单的。
比如:ds-KaTeX parse error: Expected '}', got 'EOF' at end of input: … 表示对 age 做取模计算, 是个通配符,用来承接计算结果,最终计算出分库 ds-0 … ds-n
yaml配置:
spring:
shardingsphere:
sharding:
# 表策略配置
tables:
# t_user 是逻辑表
t_user:
# 分表节点 可以理解为分表后的那些表 比如 t_user_1 ,t_user_2 ,t_user_3
actualDataNodes: mydb.t_user_$->{1..3}
tableStrategy:
inline:
# 根据哪列分表
shardingColumn: age
# 分表算法 例如:age为奇数 -> t_user_2; age为偶数 -> t_user_1
algorithmExpression: t_user_$->{age % 2 + 1}
Hint分片策略(HintShardingStrategy): 相比于其他分片策略稍有不同,这种分片策略无需配置分片键,分片键值也不再从 SQL 中解析,而是由外部指定分片信息,让 SQL 在指定的分库、分表中执行。
- ShardingSphere 通过 Hint API 实现指定操作,实际上就是把分片规则 tablerule、databaserule 由集中配置变成了个性化配置。
使用场景:
yaml配置:
spring:
shardingsphere:
sharding:
# 表策略配置
tables:
# t_user 是逻辑表
t_user:
# 分表节点 可以理解为分表后的那些表 比如 t_user_1 ,t_user_2
actualDataNodes: mydb.t_user_$->{1..2}
tableStrategy:
hint:
# 复合分库算法
algorithmClassName: com.demo.module.config.MyTableHintShardingAlgorithm
代码实现:
public class MyTableHintShardingAlgorithm implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> tableNames, HintShardingValue<Integer> hintShardingValue) {
List<String> result = new ArrayList<>();
for (Integer shardingValue : hintShardingValue.getValues()) {
result.add(shardingValue < 18 ? "t_user_1" : "t_user_2");
}
return result;
}
}
测试验证1-插入数据:
@Test void hintSaveTest() { // 清除掉上一次的规则,否则会报错 HintManager.clear(); // HintManager API 工具类实例 HintManager hintManager = HintManager.getInstance(); // 直接指定对应具体的数据库 hintManager.addDatabaseShardingValue("mydb",0); // 设置表的分片键值,自定义操作哪个分片中 hintManager.addTableShardingValue("t_user" , 18); // 在读写分离数据库中,Hint 可以强制读主库 hintManager.setMasterRouteOnly(); List<TUser> users = new ArrayList<>(3); users.add(new TUser("ACGkaka_1", "123456", 10, 3000)); users.add(new TUser("ACGkaka_2", "123456", 18, 4000)); users.add(new TUser("ACGkaka_3", "123456", 15, 6000)); users.add(new TUser("ACGkaka_4", "123456", 19, 7000)); userService.saveBatch(users); }
操作结果:
测试验证2-查询数据:
@Test void hintListTest() { // 清除掉上一次的规则,否则会报错 HintManager.clear(); // HintManager API 工具类实例 HintManager hintManager = HintManager.getInstance(); // 直接指定对应具体的数据库 hintManager.addDatabaseShardingValue("mydb",0); // 设置表的分片键值,自定义操作哪个分片中 hintManager.addTableShardingValue("t_user" , 18); // 在读写分离数据库中,Hint 可以强制读主库 hintManager.setMasterRouteOnly(); QueryWrapper<TUser> wrapper = new QueryWrapper<>(); List<TUser> users = userService.list(wrapper); System.out.println(">>>>>>>>>> 【Result】 <<<<<<<<<< "); users.forEach(System.out::println); }
操作结果:
地址: https://gitee.com/acgkaka/SpringBootExamples/tree/master/springboot-sharding-jdbc-4types
参考地址:
1.分库分表的 4 种分片策略,所有 SQL 都逃不掉的一步,https://blog.51cto.com/u_14787961/3200290
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。