# ShardingSphere分库分表

# ShardingSphere简介

Sharding就是数据分片。他的核心功能就是可以将任意数据库组合,转换成为一个分布式数据库,提供整体的数据库集群服务,本身不做数据存储,而是对其他数据库产品进行整合。

Sphere是生态的意思,这意味着ShardingSphere不是一个单独的产品或框架,而是由多个框架以及产品组成的一个完整的技术生态。目前主要的成型产品是ShardingJDBCSphereProxy两个产品,以及一个用于数据迁移的子项目ElasticJob、基于公有云的云上服务OnCloud。

目前ShardingSphere已经演进到了5.X大版本,它的核心设计哲学是连接、增强、可插拔

# ShardingJDBC客户端分库分表

定位为轻量级Java框架,在Java的JDBC层提供额外服务,使用客户端直连数据库,以jar包形式提供服务,完全兼容JDBC和各种ORM框架。

  • 适用于任何基于JDBC的ORM框架:JPA、Hibernate、Mybatis、Spring JDBC Template
  • 支持任何第三方的数据库连接池:DBCP、C3P0、HikariCP等
  • 支持任意实现JDBC规范的数据库:目前支持MySQL、PostgreSQL、Oracle、SQLserver等

# ShardingProxy服务端分库分表

定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。

  • 对应用程序完全透明,可以直接当做MySQL/PostgreSQL使用
  • 兼容MariaDB等基于MySQL协议的数据库,以及openGauss等基于Postgresql协议的数据库
  • 适用于任何兼容MySQL/Postgresql协议的客户端,如:Mysql Command Client,MySQL Workbench、Navicat等。

# JDBC和Proxy对比

ShardingSphere-JDBC ShardingSphere-Proxy
数据库 任意 MySQL/PostgreSQL
连接消耗数
异构语言 仅Java 任意
性能 损耗低 损耗略高
无中心化
静态入口

# 分库分表的优势

提高系统性能:将大型数据库分成多个小数据库,每个小数据库只需要处理部分数据

提高系统可用性:可以将数据复制到多个数据库

提高系统课扩展性:当数据量增加,只需要增加更多的数据库和表,而不是替换整个数据库

提高系统灵活性:可以根据数据的使用情况,对不同的数据库和表进行不同的优化

降低系统成本:分库分表可以使用更便宜的硬件和存储设备

# 分库分表的挑战

数据是应用的基础,随着数据被拆分到多个分片,应用层会面临很多新的问题。

  • 主键避重问题:自增ID无法保证全局唯一,需要单独设计全局主键
  • 数据备份问题:如何保持集群服务整体的稳定性
  • 数据迁移问题:业务增长必然遇到的问题,当数据库集群需要扩缩容的时候,数据也要跟着迁移
  • 分布式事务问题:数据分布在不同的库和服务器,不可避免会带来分布式事务问题
  • SQL路由问题:执行SQL语句检索的时候,如何快速定位到目标数据所在的数据库
  • 跨节点查询、归并问题:比如常见的Limit、OrderBy等等

业界目前唯一比较值得参考的详细标准,是阿里公开的开发手册中提到的诶,建议预估三年内,单表数据超过500W,或者单表数据大小超过2G,就需要考虑分库分表。

# 版本变更5.3.0

# Maven坐标

ShardingSphere 5.3.0 以前,可以直接在yml文件中配置

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>
 
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-namespace</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>

ShardingSphere 5.3.0 以后

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>${shardingsphere.version}</version>
</dependency>

在 5.3.0 版本以前,ShardingSphere-JDBC 同时支持 Java API、YAML、Spring Boot Starter 和 Spring Namespace 等配置方式,维护更新较为困难。

pom.xml示例

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.7.18</version>
            <!--解决版本冲突问题-->
            <exclusions>
                <exclusion>
                    <groupId>org.yaml</groupId>
                    <artifactId>snakeyaml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--需要手动引入 解析yaml文件-->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.7.18</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!--sharding 核心库-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core</artifactId>
            <version>5.4.1</version>
        </dependency>

# 自定义算法

移除 Spring 模块会同时移除 AlgorithmProvided 相关类。若此前用户在自定义算法中有使用到 Bean 注入相关的逻辑,更新后将失效。对需要在算法中使用 Spring Bean 的场景,需开发者主动管理。

# 事务

移除用于支持方法级别事务声明的 @ShardingSphereTransactionType 注解。

若用户有在方法级别更改事务类型的需求,请使用Java API方式。

# 配置文件

ShardingSphere 社区决定在 ShardingSphere 5.3.0 Release 中移除 Spring 全部依赖和配置支持!!!

使用方式为在application.yml文件引入官方的sharding.yaml配置文件(resources目录下)

spring:
  datasource:
    driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
    url: jdbc:shardingsphere:classpath:sharding.yaml

所以建议直接学习一下官方的yaml配置文件书写格式

Sharding5.5.1文档 (opens new window)

sharding.yaml示例

# 数据源配置
dataSources:
  m0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/coursedb1?serverTimezone=UTC
    username: root
    password: 123456
  m1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/coursedb2?serverTimezone=UTC
    username: root
    password: 123456

# 分片规则配置
rules:
- !SHARDING
  tables:
    course: # 要分片的表
      actualDataNodes: m$->{0..1}.course_$->{1..2} # 实际数据节点
      tableStrategy: # 表分片策略
        standard: # 标准分片策略,
          shardingColumn: cid
          shardingAlgorithmName: course_tbl_alg
      databaseStrategy: # 数据库分片策略
        standard: # 用于单分片键的标准分片场景
          shardingColumn: cid # 分片列名称
          shardingAlgorithmName: course_db_alg # 分片算法名称
      keyGenerateStrategy: # 主键生成策略
        column: cid
        keyGeneratorName: alg_snowflake

  # 表默认不分片
  defaultTableStrategy:
    none:
  # 默认数据库不分片
  defaultDatabaseStrategy:
    none:

  # 分片算法
  shardingAlgorithms:
    course_db_alg:
      type: INLINE
      props:
        algorithm-expression: m$->{cid%2} # 分片数量
    course_db_alg1:
      type: MOD
      props:
        sharding-count: 2 # 分片数量
    course_tbl_alg:
      type: INLINE
      props:
        algorithm-expression: course_$->{cid%2+1} # 算法的行表达式

  # 分布式序列算法配置
  keyGenerators:
    alg_snowflake:
      type: SNOWFLAKE
      props:
        worker-id: 1

props:
  sql-show: true

# 分片算法详解(补充)

# 时间范围分片

# 开发环境分库分表

# 数据源配置
dataSources:
  m0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/hzw_bridge?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 
  m1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/hzw_bridge_ais?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 

# 分片规则配置
rules:
# 加载单表
- !SINGLE
  tables:
    # MySQL 风格
    - "*.*" # 所有表都不需要分片
# 要分片的表
- !SHARDING
  tables:
    ais_move: # 要分片的表
      actualDataNodes: m1.ais_move_$->{202501..203012} # 实际数据节点
      tableStrategy: # 表分片策略
        standard: # 标准分片策略,
          shardingColumn: update_time
          shardingAlgorithmName: ais_sharding_alg
      keyGenerateStrategy: # 主键生成策略
        column: id
        keyGeneratorName: alg_snowflake

  # 默认数据库不分片
  defaultDatabaseStrategy:
    none:

  # 表默认不分片
  defaultTableStrategy:
    none:

  # 分片算法
  shardingAlgorithms:
    ais_sharding_alg:
      type: INTERVAL
      props:
        datetime-pattern: "yyyy-MM-dd HH:mm:ss" # 时间格式
        datetime-lower: "2025-01-01 00:00:00" # 时间分片下限
        datetime-upper: "2030-12-31 23:59:59" # 时间分片上限
        sharding-suffix-pattern: yyyyMM # 表后缀名
        datetime-interval-unit: MONTHS # 时间片段单位
        datetime-interval-amount: 1 # 时间片段数量

  # 分布式序列算法配置
  keyGenerators:
    alg_snowflake:
      type: SNOWFLAKE
      props:
        worker-id: 1

props:
  sql-show: true

# 使用案例

# ShardingJDBC基于Mybatis单库分表

# 引入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.10</version>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>

# yml配置

spring:
  shardingsphere:
    datasource:
      names: ds  # 数据源名称,唯一标识
      ds:
        type: com.zaxxer.hikari.HikariDataSource  # 数据源类型
        driver-class-name: com.mysql.cj.jdbc.Driver # 驱动名称
        jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
        username: root
        password: 123456
    sharding:
      tables:
        tb_user:  # 要分库分表的名称
          actual-data-nodes: ds.tb_user_$->{0..1} # 数据节点 tb_user_0 tb_user_1
          table-strategy: # 分表策略
            inline: # 内联方式
              sharding-column: id # 切片的列
              algorithm-expression: tb_user_$->{id % 2} # 奇数在tb_user_1 偶数在tb_user_0
          key-generator: # 主键生成策略
            column: id # 主键列名
            type: SNOWFLAKE
            props:
              worker: # 工作节点为123
                id: 123
      binding-tables: tb_user
      
  mybatis:
    type-aliases-package: #实体对象包全限定名
    configuration:
      cache-enabled: true
      use-generated-keys: true
      default-executor-type: REUSE
      use-actual-param-name: true

# 注意事项

  1. 数据分片后,原有的数据库自动增长ID以及ORM层框架的配置不能再使用
  2. 使用SNOWFLAKE 雪花算法,64为LONG类型,所以需要设置相关字段为BIGINT
  3. 注意项目中springboot、jdk的版本号

# ShardingJDBC基于JPA单库分表

# 引入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<dependency>
    <groupId>com.github.wenhao</groupId>
    <artifactId>jpa-spec</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>4.1.1</version>
</dependency>

# yml配置

# sharding部分配置和前文mybatis方式相同
  jpa:
    open-in-view: false # 每个请求处理完毕关闭数据库连接
    generate-ddl: false # 一般在开发阶段使用,根据entity生成数据库的ddl
    show-sql: false # 控制台不显示sql语句
    properties: 
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect  # 数据库方言
        format_sql: true 
        use-new-id-generator-mappings: false # 新的主键映射策略

# 注意事项

  1. jpa方式要使用@Table标注表名 @Repository标注dao

# ShardingJSBD基于jpa读写分离

# 读写分离介绍

设计目标:让使用方像使用一个数据库一样使用主从数据库集群

读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库。

读写分离可以提升系统的吞吐量和可用性,但也带来了数据不一致的问题

# 引入依赖

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>com.github.wenhao</groupId>
            <artifactId>jpa-spec</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

# yml配置

spring:
  shardingsphere:
    datasource:
      names: master,slave0 # 定义多个数据源
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding_master?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
        username: test
        password: test
      slave0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/test_db_sharding_slave0?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
        username: test
        password: test
    sharding:  # 分片
      tables:
        tb_user:
          database-strategy:
            inline:
              sharding-column: id
              algorithm-expression: master
          key-generator:
            column: id
            type: SNOWFLAKE
            props:
              worker:
                id: 123
 		# 其他表
    master-slave: #主从配置
        name: ms
        load-balance-algorithm-type: round_robin  # 负载均衡算法为轮询
        master-data-source-name: master # 主数据库名称
        slave-data-source-names: slave0 # 从数据库名称
    props:
      sql:
        show: true
  jpa:
    open-in-view: false
    generate-ddl: false
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect
        format_sql: true
        use-new-id-generator-mappings: false

# 注意事项

  1. 主库和从库的数据同步不是shardingJDBC做的,需要自行同步
  2. 查询操作会发生在从库,修改、添加,操作会发生在主库

原文链接 https://www.pdai.tech/md/spring/springboot/springboot-x-mysql-shardingjdbc-jpa-masterslave.html

# ShardingJDBC基于JPA的DB隔离

# 知识准备

  1. 逻辑表:水平拆分的数据库的相同逻辑和数据结构表的总称。例如订单数据有10张表,分别是t_order_0到t_order_9,逻辑表名为t_order

  2. 真实表:分片数据库中真实存在的物理表,t_order_0到t_order_9

  3. 数据节点: 数据源名称和数据表,例如ds_0.t_order_0

  4. 绑定表:分片规则一致的主表和子表,如t_order和t_order_item,均按照order_id分片

    • 绑定表之间的多表关联查询不会出现笛卡尔积
  5. 广播表: 分布式计算中,将小表的副本发送到集群的每个节点,以便提高访问效率。

  6. 分片键:用于分片的数据库字段

  7. 分片算法:精确分片算法(单字段)、范围分片算法、复合分片算法(多字段)、Hint分片算法

  8. 分片策略:标准分片策略、复合分片策略、行表达式分片策略(groovy表达式)、Hint分片策略

# yml配置

  shardingsphere:
    datasource:
      names: tenant-a,tenant-b
      tenant-a:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/test_db_tenant_a?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
        username: root
        password: 123456
      tenant-b:
			...
    sharding:
      default-database-strategy:
        hint:
          algorithm-class-name: 
          	# hint算法全限定类名
      tables:
        tb_user:
          actual-data-nodes: tenant-${['a','b']}.tb_user
          key-generator:
			...
        tb_role:
          actual-data-nodes: tenant-${['a','b']}.tb_role
          key-generator:
			...
        tb_user_role:
          actual-data-nodes: tenant-${['a','b']}.tb_user_role
          key-generator:
			...
      binding-tables: tb_user,tb_role,tb_user_role
    props:
      sql:
        show: true

# 使用

@Aspect
@Order(1)
@Component
public class TenantDatasourceAspect {
    /**
     * 定义切点.
     */
    @Pointcut("execution(* tech.pdai.springboot.shardingjdbc.jpa.tenant.dbhint.dao.*.*(..))")
    public void useTenantDSPointCut() {
        // no impl
    }
    
    // 前置逻辑
    @Before("useTenantDSPointCut()")
    public void doDs0Before() {
        HintManager.clear();
        HintManager hintManager = HintManager.getInstance();
        // 实际环境将client信息放在xxxContext中(由ThreadLocal承接),并通过client-id来获取tenant.
        // 这里为了方便演示,只是使用了tenant-a
        hintManager.setDatabaseShardingValue("tenant-a");
    }

    // 后置逻辑
    @After("useTenantDSPointCut()")
    public void doDs0after() {
        HintManager.clear();
    }
}

# 参考文档

分片算法详解 (opens new window)

Last Updated: 2/8/2025, 8:12:41 AM