ShardingSphere-Proxy 入门教程之五

大纲

前言

学习资源

ShardingSphere-Proxy 使用

水平分库使用案例

准备工作

版本说明

本节所有案例使用的组件版本如下表所示:

组件版本说明
JDK11
MySQL8.0.29
SpringBoot2.7.18
MyBatis-Plus3.3.1
ShardingSphere-Proxy5.1.1
数据库规划

本节所有案例是在两个 MySQL 数据库上实现的,数据库的规划如下图所示:

数据库服务器 IP 端口库的名称表的名称
订单数据库服务器一(server_order0192.168.2.1913310db_ordert_order0t_order1t_order_item0t_order_item1
订单数据库服务器二(server_order1192.168.2.1913311db_ordert_order0t_order1t_order_item0t_order_item1
ShardingSphere-Proxy 服务器 192.168.2.1913309sharding_db(逻辑库)t_order(逻辑表)、t_order_item(逻辑表)
数据库部署
  • 部署订单数据库服务器一(server_order0
1
2
3
4
5
6
7
8
9
10
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3310:3306 \
-v /data/server/order0/conf:/etc/mysql/conf.d \
-v /data/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
--name server_order0 \
--restart always \
mysql:8.0.29
  • 更改订单数据库服务器一(server_order0)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server_order0 env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

  • 部署订单数据库服务器二(server_order1
1
2
3
4
5
6
7
8
9
10
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3311:3306 \
-v /data/server/order1/conf:/etc/mysql/conf.d \
-v /data/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
--name server_order1 \
--restart always \
mysql:8.0.29
  • 更改订单数据库服务器二(server_order1)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server_order1 env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
数据库初始化
  • 在订单数据库服务器一(server_order0)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 创建数据库
CREATE DATABASE db_order;

-- 切换数据库
USE db_order;

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order_item0(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order_item1(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);
  • 在订单数据库服务器二(server_order1)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 创建数据库
CREATE DATABASE db_order;

-- 切换数据库
USE db_order;

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order_item0(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);

-- 创建表(水平分库时不能使用数据库的自增主键,否则不同库不同表之间会发生主键冲突)
CREATE TABLE t_order_item1(
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);

多表关联使用案例

提示

本节将演示 SpringBoot + MyBatis-Plus 如何通过 ShardingSphere-Proxy 在 水平分库 的基础上实现多表关联数据插入。水平分库会根据某个字段(或几个字段)通过特定规则,将数据分散到多个库或表中,每个分片只存储部分数据。例如,可按主键分片:偶数主键记录放入 0 库(或表),奇数主键记录放入 1 库(或表),如图所示水平分库通常是在水平分表的基础上进一步进行,将原本分散到多张表的数据继续分布到多台数据库服务器上,以提升整体的并发处理能力。完整的案例代码可以直接从 GitHub 下载对应章节 shardingsphere-proxy-lesson-04

核心概念介绍

多表关联(JOIN)能力并非 ShardingSphere-Proxy 额外提供的数据库功能,而是基于数据库本身的设计与约束。ShardingSphere-Proxy 只是对符合其分片规则与绑定表规则的 JOIN SQL 进行解析、路由与结果归并;是否能够高效甚至正确地执行多表关联,核心取决于表结构设计、分片键选择以及是否配置了绑定表,而非 ShardingSphere-Proxy 本身创造了新的关联能力。

ShardingSphere-Proxy 配置

这里的配置以水平分库为例子,演示如何使用 ShardingSphere-Proxy 提供的分片算法(包括 YAML 和 DistSQL 两种配置方式),其中分片规则如下:

  • 分库规则:

    • 订单表(t_order):
      • t_order 逻辑表中 user_id 为偶数时,数据插入到订单数据库服务器一(server_order0);user_id 为奇数时,数据插入到订单数据库服务器二(server_order1)。
      • 这样分库的好处是,同一个用户的订单数据,一定会被插入到同一台数据库服务器上,这样查询某个用户的所有订单时效率较高。
    • 订单详情表(t_order_item):
      • t_order_item 逻辑表中 user_id 为偶数时,数据插入到订单数据库服务器一(server_order0);user_id 为奇数时,数据插入到订单数据库服务器二(server_order1)。
      • 这样分库的好处是,同一个用户的订单详情数据,一定会被插入到同一台数据库服务器上,这样查询某个用户的所有订单详情时效率较高。
  • 分表规则:

    • 订单表(t_order):
      • t_order 逻辑表中 order_no 的哈希值为偶数时,数据插入对应数据库服务器的 t_order0 表;order_no 的哈希值为奇数时,数据插入对应数据库服务器的 t_order1 表。
      • 因为 order_no 是字符串类型,所以不能直接取模,需要对其进行哈希计算再取模。
    • 订单详情表(t_order_item):
      • t_order_item 逻辑表中 order_no 的哈希值为偶数时,数据插入对应数据库服务器的 t_order_item0 表;order_no 的哈希值为奇数时,数据插入对应数据库服务器的 t_order_item1 表。
      • 因为 order_no 是字符串类型,所以不能直接取模,需要对其进行哈希计算再取模。
  • 数据库规划:

特别注意

这里希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联,因此订单表和订单详情表使用相同的分库规则。无论 ShardingSphere-Proxy 是使用 YAML 配置 还是 DistSQL 配置,都必须注意 ShardingSphere-Proxy 的版本差异。不同版本之间支持的 DistSQL 命令、YAML 配置语法以及参数格式可能存在较大差异,同一份配置在不同版本下可能无法直接执行或行为不一致,因此在编写和迁移配置时必须结合 ShardingSphere-Proxy 具体版本进行验证。

YAML 配置
  • 创建或编辑 ShardingSphere-Proxy 的 conf/config-sharding.yaml 配置文件,添加以下水平分库的配置内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 逻辑数据库(Schema)的名称
schemaName: sharding_db

# 配置数据源
dataSources:
# 配置第 1 个数据源
server_order0:
url: jdbc:mysql://192.168.2.191:3310/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置第 2 个数据源
server_order1:
url: jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置数据分片规则
rules:
- !SHARDING
# 配置分片表规则
tables:
# 订单表
t_order:
# 数据节点
actualDataNodes: server_order$->{0..1}.t_order$->{0..1}
# 分库策略
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: alg_mod
# 分表策略
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: alg_hash_mod
# 分布式序列策略
keyGenerateStrategy:
column: id
keyGeneratorName: alg_snowflake

# 订单详情表
t_order_item:
# 数据节点
actualDataNodes: server_order$->{0..1}.t_order_item$->{0..1}
# 分库策略
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: alg_mod
# 分表策略
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: alg_hash_mod
# 分布式序列策略
keyGenerateStrategy:
column: id
keyGeneratorName: alg_snowflake

# 配置分片算法
shardingAlgorithms:
alg_mod:
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD
props:
sharding-count: 2

# 配置分布式序列算法
keyGenerators:
alg_snowflake:
type: SNOWFLAKE
props:
max-vibration-offset: 1
max-tolerate-time-difference-milliseconds: 10
  • ShardingSphere-Proxy 水平分库的配置内容添加完成后,需要重启 ShardingSphere-Proxy 服务(假设这里是通过 Docker 部署 ShardingSphere-Proxy 服务),否则配置不会生效
1
2
# 重启 Docker 容器
docker restart server-proxy
  • 验证 ShardingSphere-Proxy 中的配置规则是否生效(ShardingSphere-Proxy 启动时会从 YAML 配置文件中加载规则)
1
2
# 远程连接 ShardingSphere-Proxy
mysql -h192.168.2.191 -P3309 -uroot -p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 查询所有数据库
SHOW DATABASES;

-- 切换到逻辑数据库(Schema)
USE sharding_db;

-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查询所有分片算法
SHOW SHARDING ALGORITHMS;

-- 查询所有分布式序列算法
SHOW SHARDING KEY GENERATORS;
DistSQL 配置
  • 创建逻辑数据库(Schema)
1
2
3
4
5
-- 创建逻辑数据库(Schema)
CREATE DATABASE sharding_db;

-- 切换到逻辑数据库(Schema)
USE sharding_db;
  • 添加数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第一个数据源
ADD RESOURCE server_order0 (
URL="jdbc:mysql://192.168.2.191:3310/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第二个数据源
ADD RESOURCE server_order1 (
URL="jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
  • 创建分片算法
1
2
3
4
-- 创建取模算法
CREATE SHARDING ALGORITHM alg_mod (
TYPE(NAME=MOD,PROPERTIES("sharding-count"="2"))
);
1
2
3
4
-- 创建哈希取模算法
CREATE SHARDING ALGORITHM alg_hash_mod (
TYPE(NAME=HASH_MOD,PROPERTIES("sharding-count"="2"))
);
  • 创建分布式序列算法
1
2
3
4
5
6
7
-- 创建 Snowflake 雪花算法
CREATE SHARDING KEY GENERATOR alg_snowflake (
TYPE(NAME=SNOWFLAKE,PROPERTIES(
"max-vibration-offset"="1",
"max-tolerate-time-difference-milliseconds"="10"
))
);
  • 创建分片规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建订单表的分片规则
CREATE SHARDING TABLE RULE t_order (
DATANODES("server_order$->{0..1}.t_order$->{0..1}"),
DATABASE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=user_id,
SHARDING_ALGORITHM=alg_mod
),
TABLE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=order_no,
SHARDING_ALGORITHM=alg_hash_mod
),
KEY_GENERATE_STRATEGY(
COLUMN=id,
KEY_GENERATOR=alg_snowflake
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建订单详情表的分片规则
CREATE SHARDING TABLE RULE t_order_item (
DATANODES("server_order$->{0..1}.t_order_item$->{0..1}"),
DATABASE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=user_id,
SHARDING_ALGORITHM=alg_mod
),
TABLE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=order_no,
SHARDING_ALGORITHM=alg_hash_mod
),
KEY_GENERATE_STRATEGY(
COLUMN=id,
KEY_GENERATOR=alg_snowflake
)
);
  • 验证配置规则是否生效(使用 DistSQL 定义数据源和数据分片规则后,不需要重启 ShardingSphere-Proxy 服务,默认会自动生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 查询所有数据库
SHOW DATABASES;

-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查询所有分片算法
SHOW SHARDING ALGORITHMS;

-- 查询所有分布式序列算法
SHOW SHARDING KEY GENERATORS;
查看日志信息
  • 查看 ShardingSphere-Proxy 运行的实时日志信息
1
2
3
4
5
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-proxy env LANG=C.UTF-8 /bin/bash

# 查看日志信息
tail -f /opt/shardingsphere-proxy/logs/stdout.log

特别注意

在开发或者测试环境中,若希望 ShardingSphere-Proxy 输出详细的日志信息,需要在其 conf/server.yml 配置文件中添加 sql-show: true 配置项。

远程访问测试
  • 远程连接 ShardingSphere-Proxy
1
mysql -h192.168.2.191 -P3309 -uroot -p
  • 查看所有数据库
1
2
3
4
5
6
7
8
9
10
mysql> show databases;
+------------------------+
| schema_name |
+------------------------+
| sharding_db |
| mysql |
| information_schema |
| performance_schema |
| sys |
+------------------------+
  • 切换数据库
1
mysql> use sharding_db;
  • 查看所有表
1
2
3
4
5
6
7
mysql> show tables;
+-----------------------+------------+
| Tables_in_sharding_db | Table_type |
+-----------------------+------------+
| t_order_item | BASE TABLE |
| t_order | BASE TABLE |
+-----------------------+------------+
  • 查询订单数据
1
mysql> select * from t_order;
  • 查询订单详情数据
1
mysql> select * from t_order_item;

提示

执行完上述 select 操作后,可以在 ShardingSphere-Proxy 的日志信息中,查看对应的逻辑 SQL 和真实 SQL,以此判断水平分库是否生效。

SpringBoot 实战使用案例
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<mysql-connector.version>8.2.0</mysql-connector.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
创建实体类
  • 订单实体类(特别注意:分库分表场景下不能使用数据库的自增主键,必须采用分布式全局唯一 ID,否则不同库不同表之间会发生主键冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@TableName("t_order") // 逻辑表名
@Data
public class Order {

/**
* MyBatis-Plus 的 ID 策略为 None <br>
* 当 ShardingSphere-Proxy 配置了分布式序列策略,会自动注入 ID
*/
@TableId(type = IdType.NONE)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal amount;

}
  • 订单详情实体类(特别注意:分库分表场景下不能使用数据库的自增主键,必须采用分布式全局唯一 ID,否则不同库不同表之间会发生主键冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@TableName("t_order_item") // 逻辑表名
@Data
public class OrderItem {

/**
* MyBatis-Plus 的 ID 策略为 None <br>
* 当 ShardingSphere-Proxy 配置了分布式序列策略,会自动注入 ID
*/
@TableId(type = IdType.NONE)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal price;

private Integer count;

}
创建 Mapper
  • 订单 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

}
  • 订单详情 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.OrderItem;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> {

}
配置数据源
  • 创建配置文件(application.properties),配置数据源
1
2
3
4
5
6
7
8
9
10
11
# 应用名称
spring.application.name=sharding-proxy-demo

# 配置数据源(这里连接的是 ShardingSphere-Proxy,而不是 MySQL)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.2.191:3309/sharding_db?serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# 打印 MyBatis-Plus 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
测试代码
  • 多表关联数据插入测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@SpringBootTest
class ShardingTest {

@Autowired
OrderMapper orderMapper;

@Autowired
OrderItemMapper orderItemMapper;

/**
* 水平分库之多表关联:多表关联数据插入测试 <p>
* 同一个用户的订单表和订单详情表中的数据会插入到同一个数据源中,这样可以避免跨库关联
*/
@Test
public void testInsertOrder() {
for (int i = 1; i <= 2; i++) {
Order order = new Order();
order.setOrderNo("000" + i);
order.setUserId((long) i);
orderMapper.insert(order);

for (int j = 1; j <= 2; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderNo("000" + i);
orderItem.setUserId((long) i);
orderItem.setPrice(new BigDecimal(10));
orderItem.setCount(2);
orderItemMapper.insert(orderItem);
}
}
}

}
  • 测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
21:00:10.214 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order ( id, order_no, user_id ) VALUES ( 2001638617836711937, '0001', 1 )
21:00:10.214 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.214 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: INSERT INTO t_order1 ( id, order_no, user_id ) VALUES (2001638617836711937, '0001', 1)

21:00:10.245 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order_item ( id, order_no, user_id, price, count ) VALUES ( 2001638619686400001, '0001', 1, 10, 2 )
21:00:10.245 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.246 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: INSERT INTO t_order_item1 ( id, order_no, user_id, price, count ) VALUES (2001638619686400001, '0001', 1, 10, 2)

21:00:10.267 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order_item ( id, order_no, user_id, price, count ) VALUES ( 2001638619761897473, '0001', 1, 10, 2 )
21:00:10.267 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.267 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: INSERT INTO t_order_item1 ( id, order_no, user_id, price, count ) VALUES (2001638619761897473, '0001', 1, 10, 2)

21:00:10.286 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order ( id, order_no, user_id ) VALUES ( 2001638619854172161, '0002', 2 )
21:00:10.287 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.287 [ShardingSphere-Command-11] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: INSERT INTO t_order0 ( id, order_no, user_id ) VALUES (2001638619854172161, '0002', 2)

21:00:10.307 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order_item ( id, order_no, user_id, price, count ) VALUES ( 2001638619933863938, '0002', 2, 10, 2 )
21:00:10.307 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.307 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: INSERT INTO t_order_item0 ( id, order_no, user_id, price, count ) VALUES (2001638619933863938, '0002', 2, 10, 2)

21:00:10.326 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order_item ( id, order_no, user_id, price, count ) VALUES ( 2001638620017750017, '0002', 2, 10, 2 )
21:00:10.326 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:00:10.326 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: INSERT INTO t_order_item0 ( id, order_no, user_id, price, count ) VALUES (2001638620017750017, '0002', 2, 10, 2)

绑定表使用案例

提示

本节将演示 SpringBoot + MyBatis-Plus 如何通过 ShardingSphere-Proxy 在 水平分库 的基础上结合绑定表实现多表关联数据查询。绑定表是建立在 多表关联 的基础上,其作用是提高多表关联查询的效率。水平分库会根据某个字段(或几个字段)通过特定规则,将数据分散到多个库或表中,每个分片只存储部分数据。例如,可按主键分片:偶数主键记录放入 0 库(或表),奇数主键记录放入 1 库(或表),如图所示水平分库通常是在水平分表的基础上进一步进行,将原本分散到多张表的数据继续分布到多台数据库服务器上,以提升整体的并发处理能力。完整的案例代码可以直接从 GitHub 下载对应章节 shardingsphere-proxy-lesson-05

核心概念介绍
  • 在 ShardingSphere-Proxy 中,绑定表是指分片规则(包括分库规则和分表规则)完全一致的一组分片表。使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而严重影响查询效率
  • 例如:t_order 表和 t_order_item 表,均按照 order_id 进行分片(包括分库和分表),并且使用 order_id 进行关联,则此两张表互为绑定表关系。
  • 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
  • 举例说明:
    • 如果 SQL 为:
      1
      SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
    • 在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:
      1
      2
      3
      4
      5
      6
      7
      SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

      SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

      SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

      SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
    • 在配置绑定表关系,并且使用 order_id 进行关联后,路由的 SQL 应该为 2 条:
      1
      2
      3
      SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

      SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
    • 其中 t_order 表由于指定了分片条件,ShardingSphere 将会以它作为整个绑定表的主表。所有路由计算将会只使用主表的策略,那么 t_order_item 表的分片计算将会使用 t_order 的条件。
  • 总结说明:
    • 绑定表的核心目标是保证 ShardingSphere-Proxy 在执行多表关联查询时,能够计算出相同的路由目标,避免:
      • 不必要的跨库 JOIN
      • 笛卡尔积路由
      • 扫描全部数据源
    • 绑定表要求多张有关联的表在分库规则和分表规则上完全一致;否则 ShardingSphere-Proxy 无法在 JOIN 时计算出相同的路由,导致不能被视为绑定表
ShardingSphere-Proxy 配置

这里的配置以水平分库为例子,演示如何使用 ShardingSphere-Proxy 提供的分片算法(包括 YAML 和 DistSQL 两种配置方式),其中分片规则如下:

  • 分库规则:

    • 订单表(t_order):
      • t_order 逻辑表中 user_id 为偶数时,数据插入到订单数据库服务器一(server_order0);user_id 为奇数时,数据插入到订单数据库服务器二(server_order1)。
      • 这样分库的好处是,同一个用户的订单数据,一定会被插入到同一台数据库服务器上,这样查询某个用户的所有订单时效率较高。
    • 订单详情表(t_order_item):
      • t_order_item 逻辑表中 user_id 为偶数时,数据插入到订单数据库服务器一(server_order0);user_id 为奇数时,数据插入到订单数据库服务器二(server_order1)。
      • 这样分库的好处是,同一个用户的订单详情数据,一定会被插入到同一台数据库服务器上,这样查询某个用户的所有订单详情时效率较高。
  • 分表规则:

    • 订单表(t_order):
      • t_order 逻辑表中 order_no 的哈希值为偶数时,数据插入对应数据库服务器的 t_order0 表;order_no 的哈希值为奇数时,数据插入对应数据库服务器的 t_order1 表。
      • 因为 order_no 是字符串类型,所以不能直接取模,需要对其进行哈希计算再取模。
    • 订单详情表(t_order_item):
      • t_order_item 逻辑表中 order_no 的哈希值为偶数时,数据插入对应数据库服务器的 t_order_item0 表;order_no 的哈希值为奇数时,数据插入对应数据库服务器的 t_order_item1 表。
      • 因为 order_no 是字符串类型,所以不能直接取模,需要对其进行哈希计算再取模。
  • 数据库规划:

特别注意

这里希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联,因此订单表和订单详情表使用相同的分库规则。由于订单表与订单详情表的分片规则(包括分库规则和分表规则)完全相同,因此这两个表在 ShardingSphere-Proxy 中形成了绑定表关系。无论 ShardingSphere-Proxy 是使用 YAML 配置 还是 DistSQL 配置,都必须注意 ShardingSphere-Proxy 的版本差异。不同版本之间支持的 DistSQL 命令、YAML 配置语法以及参数格式可能存在较大差异,同一份配置在不同版本下可能无法直接执行或行为不一致,因此在编写和迁移配置时必须结合 ShardingSphere-Proxy 具体版本进行验证。

YAML 配置
  • 创建或编辑 ShardingSphere-Proxy 的 conf/config-sharding.yaml 配置文件,添加以下水平分库的配置内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 逻辑数据库(Schema)的名称
schemaName: sharding_db

# 配置数据源
dataSources:
# 配置第 1 个数据源
server_order0:
url: jdbc:mysql://192.168.2.191:3310/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置第 2 个数据源
server_order1:
url: jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置数据分片规则
rules:
- !SHARDING
# 配置分片表规则
tables:
# 订单表
t_order:
# 数据节点
actualDataNodes: server_order$->{0..1}.t_order$->{0..1}
# 分库策略
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: alg_mod
# 分表策略
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: alg_hash_mod
# 分布式序列策略
keyGenerateStrategy:
column: id
keyGeneratorName: alg_snowflake

# 订单详情表
t_order_item:
# 数据节点
actualDataNodes: server_order$->{0..1}.t_order_item$->{0..1}
# 分库策略
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: alg_mod
# 分表策略
tableStrategy:
standard:
shardingColumn: order_no
shardingAlgorithmName: alg_hash_mod
# 分布式序列策略
keyGenerateStrategy:
column: id
keyGeneratorName: alg_snowflake

# 配置绑定表
bindingTables:
- t_order,t_order_item

# 配置分片算法
shardingAlgorithms:
alg_mod:
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD
props:
sharding-count: 2

# 配置分布式序列算法
keyGenerators:
alg_snowflake:
type: SNOWFLAKE
props:
max-vibration-offset: 1
max-tolerate-time-difference-milliseconds: 10
  • 由于订单表(t_order)与订单详情表(t_order_item)的分片规则(包括分库规则和分表规则)完全相同,为了提高多表关联查询的效率,上面的配置内容中添加了 ShardingSphere-Proxy 的绑定表配置,如下所示:
1
2
3
4
5
6
# 配置数据分片规则
rules:
- !SHARDING
# 配置绑定表
bindingTables:
- t_order,t_order_item
  • ShardingSphere-Proxy 水平分库的配置内容添加完成后,需要重启 ShardingSphere-Proxy 服务(假设这里是通过 Docker 部署 ShardingSphere-Proxy 服务),否则配置不会生效
1
2
# 重启 Docker 容器
docker restart server-proxy
  • 验证 ShardingSphere-Proxy 中的配置规则是否生效(ShardingSphere-Proxy 启动时会从 YAML 配置文件中加载规则)
1
2
# 远程连接 ShardingSphere-Proxy
mysql -h192.168.2.191 -P3309 -uroot -p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 查询所有数据库
SHOW DATABASES;

-- 切换到逻辑数据库(Schema)
USE sharding_db;

-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查看所有绑定表规则
SHOW SHARDING BINDING TABLE RULES;

-- 查询所有分片算法
SHOW SHARDING ALGORITHMS;

-- 查询所有分布式序列算法
SHOW SHARDING KEY GENERATORS;
DistSQL 配置
  • 创建逻辑数据库(Schema)
1
2
3
4
5
-- 创建逻辑数据库(Schema)
CREATE DATABASE sharding_db;

-- 切换到逻辑数据库(Schema)
USE sharding_db;
  • 添加数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第一个数据源
ADD RESOURCE server_order0 (
URL="jdbc:mysql://192.168.2.191:3310/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第二个数据源
ADD RESOURCE server_order1 (
URL="jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
  • 创建分片算法
1
2
3
4
-- 创建取模算法
CREATE SHARDING ALGORITHM alg_mod (
TYPE(NAME=MOD,PROPERTIES("sharding-count"="2"))
);
1
2
3
4
-- 创建哈希取模算法
CREATE SHARDING ALGORITHM alg_hash_mod (
TYPE(NAME=HASH_MOD,PROPERTIES("sharding-count"="2"))
);
  • 创建分布式序列算法
1
2
3
4
5
6
7
-- 创建 Snowflake 雪花算法
CREATE SHARDING KEY GENERATOR alg_snowflake (
TYPE(NAME=SNOWFLAKE,PROPERTIES(
"max-vibration-offset"="1",
"max-tolerate-time-difference-milliseconds"="10"
))
);
  • 创建分片规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建订单表的分片规则
CREATE SHARDING TABLE RULE t_order (
DATANODES("server_order$->{0..1}.t_order$->{0..1}"),
DATABASE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=user_id,
SHARDING_ALGORITHM=alg_mod
),
TABLE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=order_no,
SHARDING_ALGORITHM=alg_hash_mod
),
KEY_GENERATE_STRATEGY(
COLUMN=id,
KEY_GENERATOR=alg_snowflake
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建订单详情表的分片规则
CREATE SHARDING TABLE RULE t_order_item (
DATANODES("server_order$->{0..1}.t_order_item$->{0..1}"),
DATABASE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=user_id,
SHARDING_ALGORITHM=alg_mod
),
TABLE_STRATEGY(
TYPE=STANDARD,
SHARDING_COLUMN=order_no,
SHARDING_ALGORITHM=alg_hash_mod
),
KEY_GENERATE_STRATEGY(
COLUMN=id,
KEY_GENERATOR=alg_snowflake
)
);
  • 创建绑定表
1
2
3
CREATE SHARDING BINDING TABLE RULES (
t_order, t_order_item
);
  • 验证配置规则是否生效(使用 DistSQL 定义数据源和数据分片规则后,不需要重启 ShardingSphere-Proxy 服务,默认会自动生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 查询所有数据库
SHOW DATABASES;

-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查看所有绑定表规则
SHOW SHARDING BINDING TABLE RULES;

-- 查询所有分片算法
SHOW SHARDING ALGORITHMS;

-- 查询所有分布式序列算法
SHOW SHARDING KEY GENERATORS;
查看日志信息
  • 查看 ShardingSphere-Proxy 运行的实时日志信息
1
2
3
4
5
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-proxy env LANG=C.UTF-8 /bin/bash

# 查看日志信息
tail -f /opt/shardingsphere-proxy/logs/stdout.log

特别注意

在开发或者测试环境中,若希望 ShardingSphere-Proxy 输出详细的日志信息,需要在其 conf/server.yml 配置文件中添加 sql-show: true 配置项。

远程访问测试
  • 远程连接 ShardingSphere-Proxy
1
mysql -h192.168.2.191 -P3309 -uroot -p
  • 查看所有数据库
1
2
3
4
5
6
7
8
9
10
mysql> show databases;
+------------------------+
| schema_name |
+------------------------+
| sharding_db |
| mysql |
| information_schema |
| performance_schema |
| sys |
+------------------------+
  • 切换数据库
1
mysql> use sharding_db;
  • 查看所有表
1
2
3
4
5
6
7
mysql> show tables;
+-----------------------+------------+
| Tables_in_sharding_db | Table_type |
+-----------------------+------------+
| t_order_item | BASE TABLE |
| t_order | BASE TABLE |
+-----------------------+------------+
  • 查询订单数据
1
mysql> select * from t_order;
  • 查询订单详情数据
1
mysql> select * from t_order_item;

提示

执行完上述 select 操作后,可以在 ShardingSphere-Proxy 的日志信息中,查看对应的逻辑 SQL 和真实 SQL,以此判断水平分库是否生效。

SpringBoot 实战使用案例
添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<mysql-connector.version>8.2.0</mysql-connector.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
创建实体类
  • 订单实体类(特别注意:分库分表场景下不能使用数据库的自增主键,必须采用分布式全局唯一 ID,否则不同库不同表之间会发生主键冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@TableName("t_order") // 逻辑表名
@Data
public class Order {

/**
* MyBatis-Plus 的 ID 策略为 None <br>
* 当 ShardingSphere-Proxy 配置了分布式序列策略,会自动注入 ID
*/
@TableId(type = IdType.NONE)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal amount;

}
  • 订单详情实体类(特别注意:分库分表场景下不能使用数据库的自增主键,必须采用分布式全局唯一 ID,否则不同库不同表之间会发生主键冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@TableName("t_order_item") // 逻辑表名
@Data
public class OrderItem {

/**
* MyBatis-Plus 的 ID 策略为 None <br>
* 当 ShardingSphere-Proxy 配置了分布式序列策略,会自动注入 ID
*/
@TableId(type = IdType.NONE)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal price;

private Integer count;

}
创建 VO 类
1
2
3
4
5
6
7
8
9
10
11
import lombok.Data;
import java.math.BigDecimal;

@Data
public class OrderVo {

private String orderNo;

private BigDecimal amount;

}
创建 Mapper
  • 订单 Mapper(特别注意:由于 ShardingSphere-Proxy 中配置了绑定表,因此这里执行多表关联查询时,必须使用分片键(order_no)进行关联(join),否则会出现笛卡尔积关联或跨库关联,从而严重影响查询效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.Order;
import com.clay.shardingjdbc.vo.OrderVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

/**
* 查询每个订单的订单号和总订单金额 <p>
* 由于 ShardingSphere-Proxy 中配置了绑定表,因此这里执行多表关联查询时,必须使用分片键("order_no")进行关联,否则会出现笛卡尔积关联或跨库关联,从而严重影响查询效率
*/
@Select({"SELECT o.order_no, SUM(i.price * i.count) AS amount",
"FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no",
"GROUP BY o.order_no"})
List<OrderVo> getOrderAmount();

}
  • 订单详情 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.OrderItem;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> {

}
配置数据源
  • 创建配置文件(application.properties),配置数据源
1
2
3
4
5
6
7
8
9
10
11
# 应用名称
spring.application.name=sharding-proxy-demo

# 配置数据源(这里连接的是 ShardingSphere-Proxy,而不是 MySQL)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.2.191:3309/sharding_db?serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# 打印 MyBatis-Plus 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
测试代码
  • 多表关联数据查询测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
class ShardingTest {

@Autowired
OrderMapper orderMapper;

/**
* 水平分库之绑定表:多表关联数据查询测试 <p>
*/
@Test
public void testGetOrderAmount() {
List<OrderVo> orderAmountList = orderMapper.getOrderAmount();
}

}
  • 测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下:
1
2
3
4
5
6
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Logic SQL: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no GROUP BY o.order_no
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
16:12:27.288 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
  • 特别注意:
    • 如果配置绑定表:测试的结果最终为 4 条 SQL(如上所示), 多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
    • 如果不配置绑定表:测试的结果最终为 8 条 SQL(如下所示),多表关联查询会出现笛卡尔积关联,关联查询效率将大大降低。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      16:16:03.942 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item0 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order0 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC
      16:16:03.943 [ShardingSphere-Command-14] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT o.order_no, SUM(i.price * i.count) AS amount FROM t_order1 o JOIN t_order_item1 i ON o.order_no = i.order_no GROUP BY o.order_no ORDER BY o.order_no ASC