前言

1、为什么要分表?
当单表数据量超过500W以上,数据库处理速度就达不到理想的效果了,数据库压力大,有可能还会出现因为sql执行时间长造成连接超时等情况。
2、为什么要使用sharding-jdbc?
无代码入侵,整合操作简单。
3、为什么要采用时间分表?
我个人觉得后期对历史冷数据进行操作的时候比较方便,例如整合 hdfs+hive做数据转移和备份,按时间分表在转移的时候不用考虑锁表,长时间占用数据库连接等情况

整合 sharding-jdbc

项目采用springboot 2.6.13,mybatis-plus 3.5.2,sharding-jdbc版本5.5.2
5.5.2版本支持mysql的 insert update,但不支持insert select…
把下面的配置复制进项目里,基本上就可以了

pom文件

  <dependencies>
        <!-- Spring Boot 基础 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>

        <!-- ShardingSphere JDBC 核心 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shardingsphere</groupId>
                    <artifactId>shardingsphere-test-util</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.yaml</groupId>
                    <artifactId>snakeyaml</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.fasterxml.jackson.dataformat</groupId>
                    <artifactId>jackson-dataformat-xml</artifactId>
                </exclusion>
            </exclusions>
            <version>5.5.2</version>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.13.5</version>
        </dependency>
    </dependencies>

sharding-db-simple.yaml

sharding-jdbc相关配置

mode:
  type: Standalone
  repository:
    type: JDBC

databaseName: mysql

dataSources:
  ds0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456

rules:
  # # 其他所有表都路由到 ds0,但是不走分片
  - !SINGLE
    tables:
      - "ds0.*"
  - !SHARDING
    tables:
      oper_detail:  #这个是需要分片的逻辑表名称
        #下面是配置分片逻辑,支持按月,按年,按id求余等,可根据自己需求来配置,${20250707..20251207} 代表的含义是表结构从20250707-20251207,每天都有一张表,但是建表逻辑需要自己写,拼接出来就是oper_detail_20250707这样
        actualDataNodes: ds0.oper_detail_${20250707..20260601}
        tableStrategy:
          standard:
            shardingColumn: create_time  #分片键,mysql表的字段,他会根据这个字段来确定是落到哪张表去执行sql
            shardingAlgorithmName: sh_algorithm #分片规则,在执行sql之前会根据com.shardingjdbc.test.config.ShardingJdbcConfig的逻辑来确定落几张表
      xxx:  #如果还有其他表需要分片,继续写就可以
        actualDataNodes: ds0.xxx_${20250707..20260601}
        tableStrategy:
          standard:
            shardingColumn: xxx  #分片键
            shardingAlgorithmName: xxx_algorithm #分片规则
    defaultTableStrategy:
      none:  # 其他表不分片
    shardingAlgorithms:
      sh_algorithm:
        type: CLASS_BASED
        props:
          algorithmClassName: com.shardingjdbc.test.config.ShardingJdbcConfig #TODO 需要改成实际的配置路径
          strategy: standard
      xxx_algorithm:
        type: CLASS_BASED
        props:
          algorithmClassName: com.shardingjdbc.test.config.xxx
          strategy: hint     #其他分片方式      
#配置是否重写sql等
props:
  sql-show: true
  sql-rewrite: true

application.yml

application.yml相关配置

server:
  port: 8080

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spring:
  application:
    name: sharding-jdbc-demo
  datasource:
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    url: jdbc:shardingsphere:classpath:sharding-db-simple.yaml
    username: root
    password: 123456
  main:
    allow-bean-definition-overriding: true

sharding数据源配置

import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.io.*;

@Configuration
public class ShardingDataSourceConfig {

    @Bean
    @Primary
    public DataSource shardingDataSource() {
        System.out.println("================== 开始创建 ShardingSphere 数据源 ==================");
        try (InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("sharding-db-simple.yaml")) {
            if (resourceAsStream == null) {
                throw new FileNotFoundException("sharding-db-simple.yaml not found in classpath");
            }
            return YamlShardingSphereDataSourceFactory.createDataSource(toByteArray(resourceAsStream));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] toByteArray(InputStream in) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int nRead;
        while ((nRead = in.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        return buffer.toByteArray();
    }
}

ShardingJdbcConfig

sharding-jdbc路由表的配置,可以在这里实现具体路由到哪张表以及路由规则,本文是以天来分表,所以下面代码也是根据天去获取路由配置,如果你使用的是月或者ID取余,也可以在doSharding实现自己的逻辑

/**
 * 这里获取到的时间是前端传过来的时间,本文采用 yyyyMMddHHmmss 格式
 * StandardShardingAlgorithm 包含了精确查询 PreciseShardingValue 和 范围查询 RangeShardingValue,如果只需要一个,就实现单独的interface就行
 */
public class ShardingJdbcConfig implements StandardShardingAlgorithm<String> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    private static String getTableDay(LocalDateTime localDateTime) {
        return localDateTime.format(TABLE_DAY);
    }
    private static DateTimeFormatter TABLE_DAY = DateTimeFormatter.ofPattern("MMdd");

    /**
     * @param availableTargetNames 从配置文件 actualDataNodes的逻辑取到的所有表名
     * @param shardingValue        查询时间
     * @return
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> shardingValue) {
        System.out.println("----------------------精确查询");
        //查询时间,前端传的时间 yyyyMMddHHmmss 截取yyyyMMdd作为拼接表名
        String date = shardingValue.getValue().substring(0, 8);
        //shardingValue.getLogicTableName()是逻辑表名,例如oper_detail 实际表名oper_detail_yyyyMMdd
        String actualTable = shardingValue.getLogicTableName() + "_" + date;
        if (availableTargetNames.contains(actualTable)) {
            return actualTable;
        } else {
            throw new UnsupportedOperationException("No matching table found for: " + date);
        }
    }

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {
        System.out.println("----------------------范围查询");
        String startValue = rangeShardingValue.getValueRange().lowerEndpoint(); // 起始时间
        String endValue = rangeShardingValue.getValueRange().upperEndpoint();   // 结束时间
        Set<String> tableRangeDay = this.getTableRangeDay(startValue, endValue, collection, rangeShardingValue.getLogicTableName());
        System.out.println(tableRangeDay);
        return tableRangeDay;
    }

    private Set<String> getTableRangeDay(String start, String end, Collection<String> collection, String table) {
        LocalDateTime startTime = LocalDateTime.parse(start, FORMATTER);
        LocalDateTime endTime = LocalDateTime.parse(end, FORMATTER);
        Set<String> result = new HashSet<>();
        // 遍历开始时间-结束时间的每一天,生成对应的表名
        while (!startTime.isAfter(endTime)) {
            String suffix = "_" + startTime.getYear() + getTableDay(startTime);
            for (String actualTable : collection) {
                if (actualTable.endsWith(suffix)) {
                    result.add(actualTable);
                }
            }
            startTime = startTime.plusDays(1);
        }
        //TODO 这里 result.add(table); table是逻辑表名,例如oper_detail,加这个的原因是我在引入shardingjdbc之前使用的是单表,之前的数据都在逻辑表里面,为了不再对之前的数据去单独做处理,就在这里也加了一个逻辑表名,每次范围查询的时候也都要去逻辑表执行一次查询,这么弄的坏处就是每次查询都多了一次逻辑表的io
        //假设前端传的时间是20250801000000-20250802235959,这里处理完所有的表名之后,得到的结果就为{"oper_detail","oper_detail_20250801","oper_detail_20250802"},如果不需要查询逻辑表oper_detail,把下面这个add(table)删了就行
        result.add(table);
        return result;
    }

    @Override
    public String getType() {
        return "CLASS_BASED";
    }

    @Override
    public void init(Properties properties) {
    }
}

MybatisPlusConfig

mybatis-plus配置文件

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

我遇到的问题

1、sharding-jdbc和其他的相比,好处就是无代码入侵,只用加一些配置就可以,但是他不支持的也很多
,低版本不支持 insert update,5.5.2虽支持insert update,不支持insert select等,具体不支持的可以去查看一下文档。
2、举例:如果按天去分表,假设今天是20250801,ymal文件中配置表逻辑为ds0.oper_detail_${20250707…20300601},那么在库里面,20250707之后的表都必须存在,否则查询会报错 table or views ‘xxx’ 不存在,也必须在项目启动前就创建表,按ID取余这个没试过,不清楚会不会报错。
3、需要自己实现手动建表逻辑,比如定时任务每天建几天之后的表。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐