ShardingSphere-JDBC 分库分表实战

3,702 阅读3分钟

配置连接环境

引入maven 依赖

本文 Sharding-JDBC 选择 4.1.1 版本,可以根据需要选择其他版本。

        <!--  MySQL 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.2</version>
        </dependency>
        <!--  Sharding-JDBC  -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

数据库连接

Sharding-JDBC 可以通过 JavaYAMLSpring 命名空间Spring Boot Starter 4种方式进行配置,本文选择较为常用的YAML的方式进行配置。

spring:
  shardingsphere:
    datasource:
      names: d1
      d1:
        type:  com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db_name?onnectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
        username: root
        password: 123456

代码编写

Mapper 映射

@Builder
@TableName("drug_info_d")
public class DrugInfoDO {
   @TableId
   private Integer drugInfoId;
   
    // ... 
}

Mapper 映射

@Repository
public interface DrugInfoDAO extends BaseMapper<DrugInfoDO> {
}

Test 类测试

@Log
@SpringBootTest
class ShardingJdbcApplicationTests {
   
   @Autowired
   private DrugInfoDAO drugInfoDAO;

   @Test
   public void testDrugInfo(){
      LambdaQueryWrapper<DrugInfoDO> queryWrapper = new LambdaQueryWrapper<>();
      List<DrugInfoDO> drugInfoDOS = drugInfoDAO.selectList(queryWrapper);
        log.info("药品表数据量 :" + drugInfoDOS.size());
   }
}

运行结果

如图可以到数据库配置已经生效,实际执行的语句如下,使用别名为 d1 配置。

ShardingSphere-SQL : Actual SQL: d1 ::: SELECT drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name FROM drug_info_d

image.png

ShardingSphere-JDBC 路由

  • 分片路由(直接路由、标准路由、笛卡尔路由)
  • 广播路由(全库路由、全库表路由、全实例路由、单播路由、阻断路由) image.png 本文主要从数据库、数据库表等方面讲解inlinestandardcomplex``````hint等数据库表分片策略。

inline 策略

yml 配置

spring:
  shardingsphere:
    datasource:
      names: d1
      d1:
        type:  com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/day?onnectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
        username: root
        password: 123456
    sharding:
      tables:
        drug_info_d: # 业务逻辑表
          actualDataNodes: d1.drug_info_d${1..3} #实际要查询的表
          tableStrategy: # 指定 drug_info_d 表分片策略,分片策略包括分片键和分片算法
            inline: 
              shardingColumn: DRUG_INFO_ID
              algorithmExpression: drug_info_d${DRUG_INFO_ID%3 + 1}
            keyGenerator: # 指定 drug_info_d 表的主键生成策略为 SNOWFLAKE
              type: SNOWFLAKE 
              column: DRUG_INFO_ID  #指定主键
      binding-tables: drug_info_d  # 绑定逻辑表
    props: #
      sql:
        show: true
  main:
    allow-bean-definition-overriding: true

配置讲解

  • drug_info_d${1..3} 等价于 drug_info_d1、drug_info_d2、drug_info_d3

数据插入

@Test
public void insert(){
    for (int i = 0; i< 10; i++) {
       DrugInfoDO build = DrugInfoDO.builder().drugInfoId(i).drugName("测试药品").dosformName("测试药品").gennameCode("123").genname("测试药品").build();
       drugInfoDAO.insert(build);
    }
}

插入数据时,完全按照配置的规则( DRUG_INFO_ID%3 + 1 )执行。

ShardingSphere-SQL : Actual SQL: d1 :::  INSERT INTO drug_info_d3  ( drug_info_id,drug_name,genname_code,genname,dosform_name ) VALUES  (?, ?, ?, ?, ?) ::: [5, 测试药品, 123, 测试药品, 测试药品]
ShardingSphere-SQL : Actual SQL: d1 :::  INSERT INTO drug_info_d2  ( drug_info_id,drug_name,genname_code,genname,dosform_name )   VALUES  (?, ?, ?, ?, ?) ::: [7, 测试药品, 123, 测试药品, 测试药品]
ShardingSphere-SQL : Actual SQL: d1 :::  INSERT INTO drug_info_d1  ( drug_info_id,drug_name,genname_code,genname,dosform_name )  VALUES  (?, ?, ?, ?, ?) ::: [9, 测试药品, 123, 测试药品, 测试药品]

查询数据

@Test
public void insert(){
    for (int i = 0; i< 10; i++) {
       DrugInfoDO build = DrugInfoDO.builder().drugInfoId(i).drugName("测试药品").dosformName("测试药品").gennameCode("123").genname("测试药品").build();
       drugInfoDAO.insert(build);
    }
}

执行结果

  • ShardingSphere-JDBC 逻辑要执行的语句从 drug_info_d 表中查询数据。
  • 实际上是从drug_info_d1、drug_info_d2、drug_info_d3 物理表中查询数据信息。
ShardingSphere-SQL : Logic SQL: SELECT  drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name  FROM drug_info_d

ShardingSphere-SQL : Actual SQL: d1 ::: SELECT  drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name  FROM drug_info_d1
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT  drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name  FROM drug_info_d2
ShardingSphere-SQL : Actual SQL: d1 ::: SELECT  drug_info_id,drug_name,drug_prodname,genname_code,genname,dosform_name,rute_name  FROM drug_info_d3

以上是使用 ShardingSphere 对数据进行水平分表的新增、查询操作。 当某属性精确/模糊查询时ShardingSphere 会对所有的分表进行遍历查询获取最终的数据信息(可以根据自己需要自行测试)。

【疑问点】当前策略下是否支持范围查询吗

  • 不支持。
  • 范围查询时,提示 Inline strategy cannot support this type sharding:RangeRouteValue(如下图),需要指定相应的策略执行。image.png

standard 策略

yml 配置

 standard_table:
   # 使用多数据库d1,d2  多表 standard_table_1, standard_table_2
   actualDataNodes: d${1..2}.standard_table_${1..2}
   databaseStrategy:
     standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
       shardingColumn: s_id
       # 范围查询
       range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
       # 精确查询
       precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
   tableStrategy:
     standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
       shardingColumn: s_id
       # 范围查询
       range-algorithm-class-name: com.flyfan.src.algorithm.StandardRangeShading
       # 精确查询
       precise-algorithm-class-name: com.flyfan.src.algorithm.StandardPreciseSharding
     keyGenerator: # 指定 standard_table 表的主键生成策略为 SNOWFLAKE
       type: SNOWFLAKE  #主键生成策略为 SNOWFLAKE
       column: s_id  #指定主键
数据库范围查询配置( StandardDBRangeShading )
  • 由于范围查询会导致数据分布在不同的数据库表结构中,实际使用时自行根据需要进行配置。
 public class StandardDBRangeShading implements RangeShardingAlgorithm<Integer> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {
       // 获取范围中的上下界(最大、最小值)
       Integer uppeValue = rangeShardingValue.getValueRange().upperEndpoint();
       rangeShardingValue.getValueRange().lowerEndpoint();
       String dbName = rangeShardingValue.getLogicTableName();
       // 返回查询时涉及的数据库名称
       return Arrays.asList("d1", "d2");
    }
 }
数据库精确查询配置( StandardDBPreciseSharding )
  • 根据需要配置数据查询从指定的数据库中查询。
 public class StandardDBPreciseSharding  implements PreciseShardingAlgorithm<Integer> {
    
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {
       
       String tableName = preciseShardingValue.getLogicTableName();
       Integer sIdValue = preciseShardingValue.getValue();
       // 实现 d1,d2
       BigInteger tableIndex = BigInteger.valueOf(sIdValue);
       BigInteger resB = tableIndex.mod(new BigInteger("2")).add(new BigInteger("1"));
       String key = "d"  + resB;
       if (collection.contains(key)){
          return key;
       }
       return null;
    }
 }
数据表范围查询配置( StandardRangeShading )
  • 范围查询导致数据分布在不同的数据表中,因而需要返回数据可能出现的所有数据表
 public class StandardRangeShading  implements RangeShardingAlgorithm<Integer> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {
       // 获取范围中的上下界(最大、最小值)
       Integer uppeValue = rangeShardingValue.getValueRange().upperEndpoint();
       rangeShardingValue.getValueRange().lowerEndpoint();
       String tableName = rangeShardingValue.getLogicTableName();
       //  standard_table_1, standard_table_2
       return Arrays.asList(tableName+"_1", tableName+"_2");
    }
 }
数据表范围查询配置( StandardPreciseSharding )
  • 精确到具体的数据表中查询数据信息
 public class StandardPreciseSharding implements PreciseShardingAlgorithm<Integer> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Integer> preciseShardingValue) {
       
       String tableName = preciseShardingValue.getLogicTableName();
       String sId = preciseShardingValue.getColumnName();
       Integer sIdValue = preciseShardingValue.getValue();
       // 实现 db_$->{s_id%2 +1}
       BigInteger tableIndex = BigInteger.valueOf(sIdValue);
       BigInteger resB = tableIndex.mod(new BigInteger("2")).add(new BigInteger("1"));
       String key = tableName +"_"+resB;
       if (collection.contains(key)){
          return key;
       }
       return null;
    }
 }

数据插入

 @Test
 public void insert(){
     for (int i = 0; i < 20; i++) {
        Long value = Long.valueOf(i);
        StandardTable build = StandardTable.builder().sId(i).sName("standard_" + i).sNo(i).sContent("standard_table_" + i).build();
        standardTableDAO.insert(build);
     }
 }

执行结果

数据将被均匀的保存在db1.standard_table_1,db2.standard_table_2 中。

image.png

image.png

查询数据

  • 只查询 s_id 为奇数的数据信息。
@Test
public void queryIn(){
   LambdaQueryWrapper<StandardTable> queryWrapper = new LambdaQueryWrapper<>();
    // 查询 1,3,5
   queryWrapper.in(StandardTable::getSId,1,3,5);
   List<StandardTable> standardTables = standardTableDAO.selectList(queryWrapper);
        standardTables.stream().forEach( st -> {
             System.out.println(st);
       });
}
  • 使用数据库表的精确查询策略。直接查询 db2 中的 standard_table_2 表。 image.png

  • 范围查询将会导致精确查询失效。

@Test
public void queryRange(){
   LambdaQueryWrapper<StandardTable> queryWrapper = new LambdaQueryWrapper<>();
   //  使用范围查询数据 
   queryWrapper.le(StandardTable::getSId,20);
   queryWrapper.ge(StandardTable::getSId,15);
   List<StandardTable> standardTables = standardTableDAO.selectList(queryWrapper);
   standardTables.stream().forEach( st -> {
      System.out.println(st);
   });
}
  • 范围查询将会根据配置扫描所有的数据库下的数据表。

image.png

complex 策略

当数据表中有某个键具有跟主分片键相同的特性时,查询时可以根据已有的主分片建,对额外的分片键进行处理,来实现指定的数据库、数据表进行数据的查询优化工作。

yml 配置

 complex_table:
   actualDataNodes: d${1..2}.complex_table_${1..2}
   databaseStrategy:
     standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
       shardingColumn: c_id
       range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
       precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
   tableStrategy:
     complex:
       # 分片键c_id,
       sharding_columns: c_id,c_no
       algorithm-class-name: com.flyfan.src.algorithm.ComplexTableShading

表分片策略配置 ( ComplexTableShading )

 public class ComplexTableShading implements ComplexKeysShardingAlgorithm<Integer> {
    
    @Override
    public Collection<String> doSharding(Collection<String> collection, ComplexKeysShardingValue<Integer> complexKeysShardingValue) {
       
       // 范围
       Range<Integer> cIdRange = complexKeysShardingValue.getColumnNameAndRangeValuesMap().get("c_id");
       Collection<Integer> cNoCol = complexKeysShardingValue.getColumnNameAndShardingValuesMap().get("c_no");
       
       Integer upperValue = cIdRange.upperEndpoint();
       Integer lowerValue = cIdRange.lowerEndpoint();
       
       List<String> result = new ArrayList<>();
        for (Integer cNo : cNoCol) {
           //
           BigInteger index = BigInteger.valueOf(cNo).mod(new BigInteger("2")).add(new BigInteger("1"));
           result.add(complexKeysShardingValue.getLogicTableName() + "_" + index);
        }
        
        log.info(result.toString());
       return result;
    }
 }

查询数据

 @Test
 public void queryIn(){
    LambdaQueryWrapper<ComplexTable> queryWrapper = new LambdaQueryWrapper<>();
    // 范围查询
    queryWrapper.between(ComplexTable::getCId,6,15);
    // 精确查询
    queryWrapper.eq(ComplexTable::getCNo,12);
     for (ComplexTable complexTable : complexTableDAO.selectList(queryWrapper)) {
        System.out.println(complexTable);
     }
 }
  • 范围查询的同时精确的在 complex_table_1 中进行查询操作。

image-20210926102953110

hint 策略

yml 配置

   hint_table:
     actualDataNodes: d${1..2}.hint_table_${1..2}
     databaseStrategy:
       standard: # 指定 standard_table 表的分片策略,分片策略包括分片键和分片算法
         shardingColumn: h_id
         range-algorithm-class-name: com.flyfan.src.algorithm.StandardDBRangeShading
         precise-algorithm-class-name: com.flyfan.src.algorithm.StandardDBPreciseSharding
     tableStrategy:
       hint: # 指定 hint_table 表的分片策略,分片策略包括分片键和分片算法
         algorithm-class-name: com.flyfan.src.algorithm.HintTableShading

表分片策略配置 ( ComplexTableShading )

 public class HintTableShading implements HintShardingAlgorithm<Integer> {
     
     @Override
     public Collection<String> doSharding(Collection<String> collection, HintShardingValue<Integer> hintShardingValue) {
         // 获取指定的表名后缀值
         Collection<Integer> tableShardingValue = hintShardingValue.getValues();
         String key = hintShardingValue.getLogicTableName() + "_"    + tableShardingValue.toArray()[0];
         if (collection.contains(key)){
             return Arrays.asList(key);
         }
         return null;
     }
 }

查询数据

 @Test
 public void queryByHint(){
    HintManager hintManager = HintManager.getInstance();
    // 设置从指定的数据表中查询数据
    hintManager.addTableShardingValue("hint_table",2);
   
    LambdaQueryWrapper<HintTable> queryWrapper = new LambdaQueryWrapper<>();
    log.info("查询到的数据大小为:" + hintTableDAO.selectList(queryWrapper).size());
    hintManager.close();
 }

从指定的数据表 hint_table_2 中查询

image-20210926103921547

总结

本文介绍了 inline、standard、complex、hint 等策略的简单使用,读者可以通过该篇文章快速了解ShardingSphere-JDBC 使用,方便根据实际业务需要进行数据的分库分表来解决遇到的问题。

参考文章 :shardingsphere.apache.org/document/cu…