2024 ShardingSphere-JDBC 保姆级分库分表,读一句就入门!



建议大家直接把代码拉下来然后cv文章中代码跑出效果慢慢体会

官方介绍

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。

Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。
Apache ShardingSphere 包含JDBCProxy,本文着重介绍JDBC。Proxy需要独立部署,用法类似于JDBC
ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。


一 分库分表概念

主要是两种:垂直拆分水平拆分。而拆分的粒度,一般又分为分库分表

1 垂直拆分

1.1 垂直分库

简单来说以表为单位,进行拆分。如下图中把一个数据库中的的6张表,拆分到3个数据库中去,每个数据库分配2张不同的表,专库专表。
在这里插入图片描述

1.1 垂直分表

把数据库中的某一张字段比较多的表,拆分成若干张表。如下图中sku表,6个字段拆分为两张表。
在这里插入图片描述

2 水平拆分

2.1 水平分库

以字段为依据,将一个库的数据拆分到多个库中。 每个库的表结构都一样。 每个库的数据都不一样。 所有库的并集是全量数据。
在这里插入图片描述

2.1 水平分表

字段不变,将一张表的数据拆分到多个表中。每个表的表结构都一样。 每个表的数据都不一样。 所有表的并集是全量数据
在这里插入图片描述

二 导入依赖

SpringBoot基本信息

  • SpringBoot 3.3.2
  • java17
  • mybatis-plus 3.5.7

根据官方文档的提示

  1. 引入 maven 依赖。
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc</artifactId>
    <version>${latest.release.version}</version>
</dependency>

截止写这篇文章为止,我在maven仓库能找到的最新的 shardingsphere-jdbc版本是5.5.0,此处使用此最新版。

  1. 在 SpringBoot 的 classpath 目录下新建一个 xx.yaml 用来编写ShardingSphere-JDBC规则
  2. 编辑application.yml
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定 YAML 配置文件
spring.datasource.url=jdbc:shardingsphere:classpath:xx.yaml

后续配置都操作classpath下的xx.yaml这个文件即可,后文描述的yml文件都是指这个文件

三 初始化数据库

为了简化操作,使用本地数据库模拟集群,把不同的数据库当成不同的服务器上的数据库即可
创建两个数据库:testshardingjdbc testshardingjdbc2 模拟两台服务器

四 代码实现

确保依赖正确添加,文件正确创建,最好启动一下项目确保没有出错再进行后续步骤

1 水平分库

假设原本数据库中有张表dish_1,水平分库之后 testshardingjdbc testshardingjdbc2这两台服务器上都有了结构一模一样的dish_1表。
现在对yml进行基础的配置:
所有的yml的配置都能从官网找到~,可以放心查阅

# 单机模式 也有集群模式 此处本机选择单机即可 这个必须添加! 不然要报错
mode:
  type: Standalone
# 开启sql日志打印
props:
  sql-show: true
# 数据源 跟你连接数据库是一个道理 不过可以连接不同的服务器下的数据库而已
dataSources:
  ds_0:  # 自定义数据源名字 任意即可
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  # 数据源完整类名(此处为默认) 你也可以换成德鲁伊是什么就填什么
    driverClassName: com.mysql.cj.jdbc.Driver  # 后面配置就是你连接数据的配置
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
  ds_1:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc2?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root

上述dataSources下面配置的就是你的数据库连接,此处模拟两台服务器,ds_0就表示第一台服务器,ds_1就表示第二台。
假设我们插入十条数据,那这十条数据该如何分配到不同数据库去呢?
接下来就配置我们的分片规则(Sharding),在yml继续写。

rules:
  - !SHARDING
    tables:
      dish:  # 逻辑表名称 名字任意即可 操作时时使用
        # 真实的数据节点 ds_0服务器下的dish_1表 ds_1服务器下的dish_1表
        actualDataNodes: ds_0.dish_1, ds_1.dish_1  
        databaseStrategy:  # 分库策略
          standard:
            shardingColumn: id  # 分片列名称 填表中的字段,此处表示dish1表中的id字段
            shardingAlgorithmName: t_dish_inline  # 分片算法名称 任意即可
	# 分片算法配置 
    shardingAlgorithms:  
      t_dish_inline:  # 分片算法名称 你上面自定义的
        type: INLINE  # 分片算法类型 INLINE  表示就写在algorithm-expression后面
        props:
          algorithm-expression: ds_${id % 2}  # 分片具体操作 id为上面指定的分片列 INLINE 模式

上述配置在之前的服务器连接配置之后,大致意思就是:你已经完成了水平分片,但是插入数据到底插插入到哪个服务器的哪张表呢?就需要配置rules了,rules里面配置actualDataNodes找到了服务器上的表,然后配置shardingColumn:id,那么插入数据的时候id就会作为分片的关键algorithm-expression配置了具体实现过程,插入数据的时候根据id%2运算,就一定是ds_0 或者 ds_1中的dish_1。

举例:

  • 插入一条id为0的数据,id%2=0ds_${id % 2}.dish_1表示ds_0.dish_1中插入一条数据
  • 插入一条id为1的数据,id%2=1ds_${id % 2}.dish_1表示ds_1.dish_1中插入一条数据

这里有一个写法ds_${id % 2}.dish_1,其实就是表示${id % 2}是一个变量,id%2结果为0或1,此处结果就是ds_0.dish_1或者ds_1.dish_1,所以actualDataNodes: ds_0.dish_1, ds_1.dish_1 可以改写为actualDataNodes: ds_${0..1}.dish_1

写法如下

# 释义:
ds${0..2}则表示:ds0、ds1、ds2
# 也可以这样写:
ds${['0','1']}
# 也可以组合起来用:
ds${['0','1']}.shoping_0${0..1}

完整yml

# 单机模式 也有集群模式 此处本机选择单机即可 这个必须添加! 不然要报错
mode:
  type: Standalone
# 开启sql日志打印
props:
  sql-show: true
# 数据源 跟你连接数据库是一个道理 不过可以连接不同的服务器下的数据库而已
dataSources:
  ds_0:  # 自定义数据源名字 任意即可
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource  # 数据源完整类名(此处为默认) 你也可以换成德鲁伊是什么就填什么
    driverClassName: com.mysql.cj.jdbc.Driver  # 后面配置就是你连接数据的配置
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
  ds_1:  
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc2?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
rules:
  - !SHARDING
    tables:
      dish:  # 逻辑表名称 名字任意即可
        # 真实的数据节点 ds_0服务器下的dish_1表 ds_1服务器下的dish_1表
        actualDataNodes: ds_0.dish_1, ds_1.dish_1  
        databaseStrategy:  # 分库策略
          standard:
            shardingColumn: id  # 分片列名称 填表中的字段,此处表示dish1表中的id字段
            shardingAlgorithmName: t_dish_inline  # 分片算法名称 任意即可
	# 分片算法配置 
    shardingAlgorithms:  
      t_dish_inline:  # 分片算法名称 你上面自定义的
        type: INLINE  # 分片算法类型 INLINE  表示就写在algorithm-expression后面
        props:
          algorithm-expression: ds_${id % 2}  # 分片具体操作 id为上面指定的分片列 INLINE 模式

测试:

    @Test
    void testInsert() {
        // 添加10个数据
        List<Dish> dishes = new ArrayList<>();
        LongStream.range(0, 10).forEach(i -> dishes.add(new Dish().setId(i).setName("测试" + i)));
        // 根据上面配置的规则 id % 2 偶数插入ds_0.dish_1 奇数插入ds_1.dish_1
        dishService.saveBatch(dishes);
    }

日志:

2024-08-16T02:27:47.901+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.901+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [0, 测试0]
2024-08-16T02:27:47.903+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.903+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_1 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [1, 测试1]
2024-08-16T02:27:47.904+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.904+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [2, 测试2]
2024-08-16T02:27:47.905+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.905+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_1 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [3, 测试3]
2024-08-16T02:27:47.905+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.905+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [4, 测试4]
2024-08-16T02:27:47.906+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.906+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_1 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [5, 测试5]
2024-08-16T02:27:47.907+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.907+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [6, 测试6]
2024-08-16T02:27:47.907+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.907+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_1 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [7, 测试7]
2024-08-16T02:27:47.908+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.908+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [8, 测试8]
2024-08-16T02:27:47.909+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T02:27:47.909+08:00  INFO 33848 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_1 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [9, 测试9]

Logic SQL 表示对逻辑表的操作
Actual SQL 就是对真实的数据库表操作

testshardingjdbc数据库

在这里插入图片描述

testshardingjdbc数据库

在这里插入图片描述

发现确实根据id来分片

2 水平分表

在testshardingjdbc上面复制一张dish表,完成水平分表,删除之前测试数据
在这里插入图片描述
yml配置

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
    
rules:
  - !SHARDING
    tables:
      dish:
        actualDataNodes: ds_0.dish_${1..2}  # 水平分表 所以数据库不用变动
        tableStrategy:  # 分库是databaseStrategy 分表是tableStrategy 注意区别
          standard:
            shardingColumn: id
            shardingAlgorithmName: t_dish_inline
    shardingAlgorithms:
      t_dish_inline:
        type: INLINE
        props:
          algorithm-expression: dish_${id % 2 + 1}  # 这里是分表的逻辑 注意区别于分库

分表的规则不同之处已经在代码注释中进行了描述 注意区别,其余逻辑不变

2024-08-16T03:06:09.841+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.841+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [0, 测试0]
2024-08-16T03:06:09.843+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.843+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_2  ( id, name )  VALUES (?, ?) ::: [1, 测试1]
2024-08-16T03:06:09.844+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.844+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [2, 测试2]
2024-08-16T03:06:09.846+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.846+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_2  ( id, name )  VALUES (?, ?) ::: [3, 测试3]
2024-08-16T03:06:09.846+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.847+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [4, 测试4]
2024-08-16T03:06:09.847+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.847+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_2  ( id, name )  VALUES (?, ?) ::: [5, 测试5]
2024-08-16T03:06:09.848+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.848+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [6, 测试6]
2024-08-16T03:06:09.849+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.849+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_2  ( id, name )  VALUES (?, ?) ::: [7, 测试7]
2024-08-16T03:06:09.850+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.850+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_1  ( id, name )  VALUES (?, ?) ::: [8, 测试8]
2024-08-16T03:06:09.850+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO dish  ( id, name )  VALUES (  ?, ?  )
2024-08-16T03:06:09.850+08:00  INFO 36784 --- [shardingJdbc] [           main] ShardingSphere-SQL                       : Actual SQL: ds_0 ::: INSERT INTO dish_2  ( id, name )  VALUES (?, ?) ::: [9, 测试9]

可以发现都是对ds_0这个服务器器上的dish_1表和dish_2表插入数据

dish_1

在这里插入图片描述

dish_2

在这里插入图片描述

3 分布式主键

官方介绍: 传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。
这里就继续拿上面的水平分库进行分布式主键代码展示!注释为新增部分

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root

rules:
  - !SHARDING
    tables:
      dish:
        actualDataNodes: ds_0.dish_${1..2}
        tableStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: t_dish_inline
        keyGenerateStrategy:  # 分布式序列策略
          column: id  # 自增列名称,缺省表示不使用自增主键生成器
          keyGeneratorName: snowflake  # 分布式序列算法名称 任意即可
    shardingAlgorithms:
      t_dish_inline:
        type: INLINE
        props:
          algorithm-expression: dish_${id % 2 + 1}
    keyGenerators:
      snowflake:  # 上面自定义算法名称
        type: SNOWFLAKE  # 雪花算法

清空表数据;修改测试方法

    @Test
    void testInsert() {
        // 添加10个数据
        List<Dish> dishes = new ArrayList<>();
        LongStream.range(0, 10).forEach(i -> dishes.add(new Dish().setName("测试" + i)));
        // 分片规则:id % 2
        dishService.saveBatch(dishes);
    }

dish_1

在这里插入图片描述

dish_2

在这里插入图片描述

偶数落到了1表,奇数落到了2表没有问题

4 广播表

广播表可以理解为每服务器上的数据库需要这个表~
用一张test表来进行测试,每个服务器都需要一张test表,那么此时就可以配置为广播表
testbroadcast表

在这里插入图片描述
yml

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc2?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root

rules:
  - !BROADCAST
    tables: # 广播表规则列表
      - testbroadcast

测试代码

    @Test
    void testInsertBroadcast() {
        // 添加10个数据
        List<Testbroadcast> broadcasts = new ArrayList<>();
        LongStream.range(0, 10).forEach(i -> broadcasts.add(new Testbroadcast().setId(i).setName("广播表" + i)));
        testbroadcastService.saveBatch(broadcasts);
    }

他会根据你配置的数据源给每个数据库对应的表插入相同的数据
结果

在这里插入图片描述

在这里插入图片描述

5 绑定表

指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。
建议大家去看官方解释比较清楚,我就不粘过来了。举例一句话概括就是:对于同一个订单id产生的订单表和订单详情表是一个整体,避免关联查询时,多次查询造成的笛卡尔积,所以需要进行绑定,减少查询次数。

链接: 官方解释绑定表

关键代码已经注释出来,核心不变

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc2?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root

rules:
  - !SHARDING
    tables:
      order:
        actualDataNodes: ds_${0..1}.order
        databaseStrategy:
          standard:
            shardingColumn: id  # 两张表分片键相同
            shardingAlgorithmName: t_order_database
      order_detail:
        actualDataNodes: ds_${0..1}.order_detail
        databaseStrategy:
          standard:
            shardingColumn: order_id  # 两张表分片键相同
            shardingAlgorithmName: t_order_detail_database
    defaultKeyGenerateStrategy:
      column: id
      keyGeneratorName: snowflake

    shardingAlgorithms:
      t_order_database:
        type: INLINE
        props:
          algorithm-expression: ds_${id % 2}
      t_order_detail_database:
        type: INLINE
        props:
          algorithm-expression: ds_${order_id % 2}
    bindingTables:  # 绑定表
      - order, order_detail
    keyGenerators:
      snowflake:
        type: SNOWFLAKE

可以参考官方解释,自行编写代码测试

6 单表

有些需要分库分表,有些不需要怎么办?指定为单表即可!

ds_0数据源的表情况

在这里插入图片描述
例如如下yml

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
    
rules:
  - !BROADCAST
    tables: # 广播表规则列表
      - testbroadcast

假设现在这个数据库没有分库分表的操作,此时我们对dish_1表插入一条数据

    @Test
    void testSelect() {
        dishService.save(new Dish().setId(1L).setName("测试"));
    }

Error preparing statement. Cause: org.apache.shardingsphere.infra.exception.kernel.metadata.TableNotFoundException: Table or view ‘dish_1’ does not exist.

显然报错了

设为单表

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
    
rules:
  - !BROADCAST
    tables: # 广播表规则列表
      - testbroadcast
  - !SINGLE
   tables:
     # MySQL 风格
     - ds_0.* # 加载指定数据源中的全部单表

插入成功
在这里插入图片描述

根据需求自行配置即可,不进行分表分库的一定要设置为单表,否则会因为没有配置分片策略而报错;

7 没有垂直分库分表的代码?

垂直分库的概念与微服务架构的思想非常相似。在微服务架构中,通常的做法是为每个服务配备一个专门的数据库,以支持其特定的业务领域。如果后续需要进一步拆分数据库,相应的也应该有对应的服务与之匹配;而不是让一个服务同时操作属于不同业务领域的数据,比如用户表和商品表。既然一个服务能够操作这些表,那么为什么要将它们分开呢?
因此,即使一个服务理论上可以操作多个业务领域的数据,但在实践中,为了遵循微服务架构的原则和最佳实践,通常会将这些数据进行垂直分库,以支持服务的职责单一原则。
垂直分表的概念可以理解为在设计数据库表结构的初期就应该考虑好。例如,将订单表和订单详情表设计为两张表,而不是一开始就设计成一张表,这样做是否有些欠妥呢?实际上,这正是一个在设计阶段就已经考虑进去的过程。

如果实际上有这种需求,根据需求走就行了。

8 既要水平分库又要水平分表如何实现?

核心不变要什么配置什么就行了~
defaultKeyGenerateStrategy表示默认,如果没有配置key生成策略,就走这里

mode:
  type: Standalone

props:
  sql-show: true

dataSources:
  ds_0:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root
  ds_1:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/testshardingjdbc2?serverTimezone=Asia/Shanghai&userUnicode=true&characterEncoding=utf-8&ssl-mode=REQUIRED
    username: root
    password: root

rules:
  - !SHARDING
    tables:
      dish:
        actualDataNodes: ds_${0..1}.dish_${1..2}
        databaseStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: t_dish_database
        tableStrategy:
          standard:
            shardingColumn: id
            shardingAlgorithmName: t_dish_table
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake
    defaultKeyGenerateStrategy:
      column: id
      keyGeneratorName: snowflake

    shardingAlgorithms:
      t_dish_database:
        type: INLINE
        props:
          algorithm-expression: ds_${id % 2}
      t_dish_table:
        type: INLINE
        props:
          algorithm-expression: dish_${ (id % 4).intdiv(2) + 1 }

    keyGenerators:
      snowflake:
        type: SNOWFLAKE

在官方的FAQ中提到: Java的整数相除结果是整数,但是对于 inline 表达式中的 Groovy 语法则不同,整数相除结果是浮点数。 想获得除法整数结果需要将 A/B 改为 A.intdiv(B)

测试代码:

    @Test
    void testInsert() {
        // 添加10个数据
        List<Dish> dishes = new ArrayList<>();
        LongStream.range(0, 10).forEach(i -> dishes.add(new Dish().setName("测试" + i)));
        // 分片规则:id % 2
        dishService.saveBatch(dishes);
    }

结果:

testshardingjdbc

dish_1

在这里插入图片描述
dish_2

在这里插入图片描述

testshardingjdbc2

dish_1

在这里插入图片描述
dish_2

在这里插入图片描述
一句话具体如何分布取决于你的配置,均匀不均匀取决于你的算法

后续可能会在此文章基础上继续添加自定义分片算法读写分离等等

感谢阅读,欢迎提出问题!

源码

强烈建议在此基础上进行练习,减少不必要的操作,直接cv代码慢慢体会

链接: github

参考文献

链接: shardingsphere 官网
链接: 黑马程序员 MySQL数据库入门到精通

  • 34
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值