ShardingSphere-JDBC 入门教程之四

大纲

前言

学习资源

ShardingSphere-JDBC 分布式序列算法

水平分片(包括水平分库和水平分表)需要关注分布式序列(即分布式全局唯一 ID),因为不能简单的使用基于数据库的自增主键,否则不同库不同表之间会发生主键冲突。通常主键冲突有两种解决方案:一种是使用 MyBatis-Plus 的 ID 生成策略;一种是使用 ShardingSphere-JDBC 的分布式序列配置。

MyBatis-Plus 的 ID 生成策略

  • 使用 MyBatis-Plus 的 ID 生成策略 ASSIGN_ID(默认是基于 Snowflake 算法实现)
1
2
3
4
5
6
7
8
9
10
11
12
@TableName("t_order")
@Data
public class Order {

/**
* 不依赖数据库自增主键,插入数据时由 MyBatis-Plus 自动生成主键
* 默认使用雪花算法(Snowflake)生成 64 位的长整型 ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;

}
  • 在 MyBatis-Plus 中,Snowflake 算法生成的 ID 是一个 64 位长整型(| 1 bit 符号位 | 41 bit 时间戳 | 5 bit 数据中心 ID | 5 bit 工作机器 ID | 12 bit 序列号 |),其默认的 Snowflake 参数为:
参数默认值说明
dataCenterId1数据中心 ID(0 ~ 31)
workerId1工作机器 ID(0 ~ 31)
  • 在 MyBatis-Plus 中,Snowflake 参数的默认值在分布式部署时可能会出现 ID 冲突,因此通常需要自定义 Snowflake 参数(包括 dataCenterIdworkerId),避免 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
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
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Sequence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;
import java.net.NetworkInterface;

@Configuration
public class SnowflakeConfig {

/**
* 定义一个 Bean 来覆盖 MyBatis-Plus 默认的 IdentifierGenerator
*/
@Bean
public IdentifierGenerator identifierGenerator() {
return new IdentifierGenerator() {

// 动态获取 Snowflake 参数
private final Sequence sequence = new Sequence(getWorkerId(), getDatacenterId());

@Override
public Number nextId(Object entity) {
return sequence.nextId();
}

};
}

/**
* 获取工作机器 ID(0~31)
*/
private long getWorkerId() {
try {
// 根据 IP 或容器主机名计算工作机器 ID(0 ~ 31)
String hostAddress = InetAddress.getLocalHost().getHostAddress();
return (hostAddress.hashCode() & 31);
} catch (Exception e) {
// 异常情况返回默认值 0
return 0;
}
}

/**
* 获取数据中心 ID(0~31)
*/
private long getDatacenterId() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress);

if (ni == null) {
// 返回默认值 0
return 0;
}

byte[] mac = ni.getHardwareAddress();
if (mac == null || mac.length == 0) {
// 返回默认值 0
return 0;
}

// 根据 MAC 地址计算数据中心ID(0 ~ 31)
return (mac[mac.length - 1] & 31);
} catch (Exception e) {
// 异常情况返回默认值 0
return 0;
}
}

}

ShardingSphere 的分布式序列

ShardingSphere-JDBC 内置了两种分布式序列算法(即分布式 ID 生成算法),包括 SNOWFLAKEUUID,可用于生成分布式全局唯一 ID。

SNOWFLAKE 算法

  • 配置 ShardingSphere-JDBC 使用 SNOWFLAKE 分布式序列算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ----------分布式序列策略配置----------

# 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分布式序列算法名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake

# ----------分布式序列算法配置----------

# 分布式序列算法类型
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
# 分布式序列算法属性配置
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.max-vibration-offset=1
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.max-tolerate-time-difference-milliseconds=10
  • 此时,需要将实体类中的 MyBatis-Plus 的 ID 生成策略修改为 AUTO 或者 NONE
1
2
3
4
5
6
7
8
9
10
11
12
@TableName("t_order")
@Data
public class Order {

/**
* 当项目中配置了 ShardingSphere-JDBC 的分布式序列时,会自动使用 ShardingSphere-JDBC 的分布式序列
* 当项目中没有配置 ShardingSphere-JDBC 的分布式序列时,自动依赖数据库的主键自增策略
*/
@TableId(type = IdType.AUTO)
private Long id;

}
  • ShardingSphere-JDBC 的 SNOWFLAKE 分布式序列算法支持配置以下属性:
属性名称数据类型说明默认值
max-vibration-offsetint最大抖动上限值,范围是 [0, 4096)
注意:若使用此算法生成值作分片值(分片列的值),建议配置此属性。此算法在不同毫秒内所生成的 Key 取模 2^n(这里的 2^n 一般为分库或分表数量)之后结果总为 0 或 1。
该属性的作用是为了让同一毫秒生成的 ID,最终的分片取模结果不总是落在同一个库 / 表上,用于避免分片倾斜(热点分片)问题。
为防止上述分片问题,建议将此属性值配置为 (2^n) - 1
1
max-tolerate-time-difference-millisecondslong最大容忍时钟回退时间,单位:毫秒 10
  • SNOWFLAKE 分布式序列算法中,max-vibration-offset 属性的配置示例:
分库数量 2^nmax-vibration-offset 的建议值 (2^n) - 1
2 库 2¹=21
4 库 2²=43
8 库 2³=87
16 库 2⁴=1615
32 库 2⁵=3231

总结

在 ShardingSphere-JDBC 的 SNOWFLAKE 分布式序列算法中,max-vibration-offset 属性用来给 Snowflake ID 在同一毫秒内增加随机性,避免分片时总落到某几个库 / 表,防止热点库 / 表的出现。

UUID 算法

  • 配置 ShardingSphere-JDBC 使用 UUID 分布式序列算法
1
2
3
4
5
6
7
8
9
10
11
# ----------分布式序列策略配置----------

# 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分布式序列算法名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_uuid

# ----------分布式序列算法配置----------

# 分布式序列算法类型
spring.shardingsphere.rules.sharding.key-generators.alg_uuid.type=UUID
  • 此时,需要将实体类中的 MyBatis-Plus 的 ID 生成策略修改为 NONE
1
2
3
4
5
6
7
8
9
10
11
12
@TableName("t_order")
@Data
public class Order {

/**
* MyBatis-Plus 不自动生成 ID
* ID 由 ShardingSphere-JDBC 自动填充
*/
@TableId(type = IdType.NONE)
private String id;

}

ShardingSphere-JDBC 分片算法

分片算法的类型

ShardingSphere-JDBC 内置的分片算法如下所示:

  • 自动分片算法

    • 取模分片算法(MOD
      • 概述:对分片键取模,决定路由的库 / 表序号。
      • 特点:算法简单、性能高、均匀分布。
      • 适用场景:用户 ID、订单 ID 等数值型字段且写请求均匀分布的场景。
    • 哈希取模分片算法(HASH_MOD
      • 概述:对分片键先做哈希,再取模。
      • 特点:分布更均匀,避免 ID 递增导致热点。
      • 适用场景:字符串类型分片键(如手机号、Email、UUID)。
    • 基于分片容量的范围分片算法(VOLUME_RANGE
      • 概述:按单分片最大承载容量进行范围分片。
      • 特点:按数据量增长自动落到不同库 / 表。
      • 适用场景:数据量随时间增长、按容量控制每个分片大小(如日志类业务)。
    • 基于分片边界的范围分片算法(BOUNDARY_RANGE
      • 概述:在配置中预先定义各分片的边界范围。
      • 特点:控制精确,可与业务范围区间对应。
      • 适用场景:省份编码、分等级、金额区间等明确范围的业务。
    • 自动时间段分片算法(AUTO_INTERVAL
      • 概述:按时间自动生成分片,比如每天 / 每月自动建表。
      • 特点:避免手动扩容,时间分片自动推进。
      • 适用场景:日志表、订单表、流水表的按时间分表。
  • 标准分片算法

    • 行表达式分片算法(INLINE
      • 概述:使用 Groovy 表达式生成路由,如 t_order_$->{user_id % 4}
      • 特点:灵活、配置简单、可自定义表达式。
      • 适用场景:分片条件规则清晰,可用简单表达式定义的场景。
    • 时间范围分片算法(INTERVAL
      • 概述:通过时间区间计算应该路由到哪个时间分片。
      • 特点:支持 “时间 → 表名” 的表达式映射。
      • 适用场景:按年 / 月 / 天的时间序列数据分表(如日志、订单流水)。
  • 复合分片算法

    • 复合行表达式分片算法(COMPLEX_INLINE
      • 概述:支持多个分片键,通过表达式组合路由。
      • 特点:多维条件路由,灵活性高。
      • 适用场景:订单场景:需按 user_idorder_time 等联合多个键决定路由。
  • Hint 分片算法

    • Hint 行表达式分片算法(HINT_INLINE
      • 概述:基于 Hint(SQL 注入式强制路由值)的表达式算法。
      • 特点:程序员通过代码显式指定分片,不依赖 SQL 条件。
      • 适用场景:
        • SQL 本身没有分片字段(例如跨库 JOIN 时强制路由)
        • 使用中间件或服务层统一指定分片键
        • 灰度 / 运营类特殊请求

自定义类分片算法

ShardingSphere-JDBC 除了提供内置的分片算法,还支持开发者自定义类分片算法(CLASS_BASED),可通过配置分片策略类型和算法类名,实现自定义扩展。

分片算法的使用

特别注意

ShardingSphere-JDBC 内置的分片算法同时适用于垂直分片(垂直分库、垂直分表)和水平分片(水平分库、水平分表)。由于篇幅限制,下面仅展示部分分片算法的配置示例。

水平分库

这里以水平分库为例子,演示如何使用 ShardingSphere-JDBC 提供的分片算法进行分库。

  • 分库规则:

    • t_order 逻辑表中 user_id 为偶数时,数据插入到订单数据库服务器一(server-order0);user_id 为奇数时,数据插入到订单数据库服务器二(server-order1)。
    • 这样分库的好处是,同一个用户的订单数据,一定会被插入到同一台数据库服务器上,这样查询某个用户的所有订单时效率较高。
    • 值得一提的,由于这里每个库只有一张表,所以只需要配置分库策略(database-strategy),所以不需要配置分表策略(table-strategy)。
  • 数据结构:

    1
    2
    3
    4
    server-order0
    └── t_order
    server-order1
    └── t_order
行表达式分片算法
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
# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里假设有两个库(server-order0、server-order1),每个库只有一张表(t_order)
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order

# ----------分库策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_inline_userid

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.type=INLINE
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_inline_userid.props.algorithm-expression=server-order$->{user_id % 2}
取模分片算法
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
# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里假设有两个库(server-order0、server-order1),每个库只有一张表(t_order)
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order

# ----------分库策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_mod

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2
哈希取模分片算法
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
# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里假设有两个库(server-order0、server-order1),每个库只有一张表(t_order)
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order

# ----------分库策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_hash_mod

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

水平分表

这里以水平分表为例子,演示如何使用 ShardingSphere-JDBC 提供的分片算法进行分表。由于篇幅有限,这里仅介绍哈希取模分片算法的使用,其他分片算法可以参考上面 水平分库 的使用。

  • 分表规则:

    • t_order 逻辑表中 order_no 的哈希值为偶数时,数据插入对应数据库服务器的 t_order0 表;order_no 的哈希值为奇数时,数据插入对应数据库服务器的 t_order1 表。
    • 值得一提的是,由于这里只有一个数据库(即单库分表),所以只需要配置分表策略(table-strategy),不需要配置分库策略(database-strategy)。
    • 因为 order_no 是字符串类型,所以不能直接取模,需要对其进行哈希计算再取模。
  • 数据结构:

    1
    2
    3
    server-order0
    ├── t_order0
    └── t_order1
哈希取模分片算法
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
# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里假设只有一个库(server-order0),该库有两张表(t_order0、t_order1)
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order$->{0..1}

# ----------分表策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

水平分库与水平分表

这里以水平分库 + 水平分表为例子,演示如何使用 ShardingSphere-JDBC 提供的分片算法。

  • 分库规则:

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

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

    1
    2
    3
    4
    5
    6
    server-order0
    ├── t_order0
    └── t_order1
    server-order1
    ├── t_order0
    └── t_order1
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
# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里假设有两个库(server-order0、server-order1),每个库有两张表(t_order0、t_order1)
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}

# ----------分库策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_mod

# ----------分表策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

ShardingSphere-JDBC 使用

水平分库使用案例

提示

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

准备工作

版本说明

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

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

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

数据库服务器 IP 端口库的名称表的名称
订单数据库服务器一(server-order0192.168.2.1913310db_ordert_order0t_order1
订单数据库服务器二(server-order1192.168.2.1913311db_ordert_order0t_order1
数据库部署
  • 部署订单数据库服务器一(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
-- 创建数据库
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)
);
  • 在订单数据库服务器二(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
-- 创建数据库
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)
);

案例代码

添加依赖
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
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<shardingsphere.version>5.1.1</shardingsphere.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>
<!-- ShardingSphere-Jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.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 {

/**
* 当项目中配置了 ShardingSphere-JDBC 的分布式序列策略时,会自动使用 ShardingSphere-JDBC 的分布式序列策略
* 当项目中没有配置 ShardingSphere-JDBC 的分布式序列策略时,自动依赖数据库的主键自增策略
*/
@TableId(type = IdType.AUTO)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal amount;

}
创建 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> {

}
配置数据分片

这里的配置以水平分库为例子,演示如何使用 ShardingSphere-JDBC 提供的分片算法,其中分片规则如下:

  • 分库规则:

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

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

  • 创建配置文件(application.properties),配置数据分片
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
# ----------基础配置----------

# 应用名称
spring.application.name=sharging-jdbc-demo
# 开发环境设置
spring.profiles.active=dev
# 模式配置(可选值:Memory、Standalone、Cluster)
spring.shardingsphere.mode.type=Memory
# 打印SQL语句
spring.shardingsphere.props.sql-show=true

# ----------数据源配置----------

# 配置真实数据源
spring.shardingsphere.datasource.names=server-order0,server-order1

# 配置第 1 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order0.jdbc-url=jdbc:mysql://192.168.2.191:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456

# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.server-order1.jdbc-url=jdbc:mysql://192.168.2.191:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456

# ----------标椎分片表配置----------

# 标准分片表配置(数据节点),这里有两个库(server-order0、server-order1),每个库有两张表(t_order0、t_order1)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由数据源名 + 表名组成,以小数点分隔;多个表以逗号分隔,支持行表达式
# <table-name>:逻辑表名
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order0.t_order0,server-order0.t_order1,server-order1.t_order0,server-order1.t_order1

# ----------分库策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_mod

# ----------分表策略配置----------

# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_no
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_hash_mod

# ----------分片算法配置----------

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2

# 分片算法类型
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.type=HASH_MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_hash_mod.props.sharding-count=2

# ----------分布式序列策略配置----------

# 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分布式序列算法名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake

# ----------分布式序列算法配置----------

# 分布式序列算法类型
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
# 分布式序列算法属性配置
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.max-vibration-offset=1
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.props.max-tolerate-time-difference-milliseconds=10
  • 为了简化标准分片表的配置,ShardingSphere JDBC 提供了行表达式,其使用教程可以看 这里。使用行表达式后,上面的标准分片表配置(数据节点)可以改写为以下形式:
1
2
3
4
5
# 标准分片表配置(数据节点)
# spring.shardingsphere.rules.sharding.tables.<table-name>.actual-data-nodes=值
# 值由数据源名 + 表名组成,以小数点分隔;多个表以逗号分隔,支持行表达式
# <table-name>:逻辑表名
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}

测试代码

插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
class ShardingTest {

@Autowired
OrderMapper orderMapper;

/**
* 水平分库:插入数据测试
*/
@Test
public void testInsertOrder() {
for (int i = 1; i <= 5; i++) {
Order order = new Order();
order.setOrderNo("000" + i);
order.setUserId((long) i);
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}
}

}

测试代码运行后的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2022-09-08 20:34:21.671  INFO 8730 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_order (order_no, user_id, amount) VALUES (?, ?, ?)
2022-09-08 20:34:21.671 INFO 8730 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-09-08 20:34:21.671 INFO 8730 --- [main] ShardingSphere-SQL : Actual SQL: server-order1 ::: INSERT INTO t_order1 (order_no, user_id, amount, id) VALUES (?, ?, ?, ?) ::: [0001, 1, 100, 1205930018975252480]

2022-09-08 20:34:21.878 INFO 8730 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_order (order_no, user_id, amount) VALUES (?, ?, ?)
2022-09-08 20:34:21.878 INFO 8730 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-09-08 20:34:21.879 INFO 8730 --- [main] ShardingSphere-SQL : Actual SQL: server-order0 ::: INSERT INTO t_order0 (order_no, user_id, amount, id) VALUES (?, ?, ?, ?) ::: [0002, 2, 100, 1205930020057382913]

2022-09-08 20:34:21.893 INFO 8730 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_order (order_no, user_id, amount) VALUES (?, ?, ?)
2022-09-08 20:34:21.893 INFO 8730 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-09-08 20:34:21.893 INFO 8730 --- [main] ShardingSphere-SQL : Actual SQL: server-order1 ::: INSERT INTO t_order1 (order_no, user_id, amount, id) VALUES (?, ?, ?, ?) ::: [0003, 3, 100, 1205930020116103168]

2022-09-08 20:34:21.903 INFO 8730 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_order (order_no, user_id, amount) VALUES (?, ?, ?)
2022-09-08 20:34:21.904 INFO 8730 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-09-08 20:34:21.904 INFO 8730 --- [main] ShardingSphere-SQL : Actual SQL: server-order0 ::: INSERT INTO t_order0 (order_no, user_id, amount, id) VALUES (?, ?, ?, ?) ::: [0004, 4, 100, 1205930020162240513]

2022-09-08 20:34:21.914 INFO 8730 --- [main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_order (order_no, user_id, amount) VALUES (?, ?, ?)
2022-09-08 20:34:21.914 INFO 8730 --- [main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
2022-09-08 20:34:21.914 INFO 8730 --- [main] ShardingSphere-SQL : Actual SQL: server-order1 ::: INSERT INTO t_order1 (order_no, user_id, amount, id) VALUES (?, ?, ?, ?) ::: [0005, 5, 100, 1205930020204183552]
查询所有数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class ShardingTest {

@Autowired
OrderMapper orderMapper;

/**
* 水平分库:查询所有数据 <br>
* 查询了两个数据源,每个数据源中使用 UNION ALL 连接两个表
*/
@Test
public void testShardingSelectAll() {
List<Order> orders = orderMapper.selectList(null);
}

}

测试代码运行后的输出结果如下:

1
2
3
4
2022-09-08 20:50:58.805  INFO 34995 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT  id,order_no,user_id,amount  FROM t_order
2022-09-08 20:50:58.805 INFO 34995 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
2022-09-08 20:50:58.806 INFO 34995 --- [ main] ShardingSphere-SQL : Actual SQL: server-order0 ::: SELECT id,order_no,user_id,amount FROM t_order0 UNION ALL SELECT id,order_no,user_id,amount FROM t_order1
2022-09-08 20:50:58.806 INFO 34995 --- [ main] ShardingSphere-SQL : Actual SQL: server-order1 ::: SELECT id,order_no,user_id,amount FROM t_order0 UNION ALL SELECT id,order_no,user_id,amount FROM t_order1
根据条件查询数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@SpringBootTest
class ShardingTest {

@Autowired
OrderMapper orderMapper;

/**
* 水平分库:根据 user_id(分片键)查询记录 <br>
* 查询了一个数据源,每个数据源中使用 UNION ALL 连接两个表
*/
@Test
public void testShardingSelectByUserId() {
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("user_id", 1L);
List<Order> orders = orderMapper.selectList(orderQueryWrapper);
orders.forEach(System.out::println);
}

}

测试代码运行后的输出结果如下:

1
2
3
2022-09-08 20:53:56.433 INFO 35828 --- [main] ShardingSphere-SQL : Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE (user_id = ?)
2022-09-08 20:53:56.434 INFO 35828 --- [main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
2022-09-08 20:53:56.435 INFO 35828 --- [main] ShardingSphere-SQL : Actual SQL: server-order1 ::: SELECT id,order_no,user_id,amount FROM t_order0 WHERE (user_id = ?) UNION ALL SELECT id,order_no,user_id,amount FROM t_order1 WHERE (user_id = ?) ::: [1, 1]