目录
2.5 创建server-order0和server-order1容器
2.8.1.测试server_order0.t_order0节点
2.8.2.测试server_order0.t_order1节点
2.8.3.测试server_order1.t_order0节点
2.8.4.测试server_order1.t_order1节点
一. 在Docker中安装ShardingSphere
接下来我将讲解ShardingSphere在Docker里面的安装
创建Docker容器所需的映射目录
我们必须提前进行创建
mkdir -p /bit/shardingsphere/proxy/conf
mkdir -p /bit/shardingsphere/proxy/ext-lib
mkdir -p /bit/shardingsphere/proxy/logs
创建Docker容器
这个 docker run
命令是用来安装并启动一个 ShardingSphere-Proxy 容器的。ShardingSphere-Proxy 是一个透明的数据库代理,它允许你像连接普通 MySQL 服务器一样连接它,但它背后可以管理多个数据库实例,实现分库分表、读写分离等高级功能。
docker run -d \
-p 3307:3307 \
-v /bit/shardingsphere/proxy/conf:/opt/shardingsphere-proxy/conf \
-v /bit/shardingsphere/proxy/ext-lib:/opt/shardingsphere-proxy/ext-lib \
-v /bit/shardingsphere/proxy/logs:/opt/shardingsphere-proxy/logs \
-e JVM_OPTS="-Xms256m -Xmx256m -Xmn128m" \
--name ss-proxy \
apache/shardingsphere-proxy:5.3.2
这些参数啥意思呢? 我们看看
docker run -d \
docker run
:Docker 命令,用于创建并启动一个新的容器。-d
:表示以后台(detached)模式运行容器。容器启动后,命令行会立即返回,不会阻塞。
-p 3307:3307 \
-p
:用于映射端口。3307:3307
:将宿主机(Host)的3307
端口映射到容器(Container)内部的3307
端口。这意味着,外部应用程序(如你的应用代码、MySQL 客户端工具)需要连接宿主机的3307
端口来访问 ShardingSphere-Proxy 服务。
-v /bit/shardingsphere/proxy/conf:/opt/shardingsphere-proxy/conf \
-v
:用于创建数据卷(Volume),实现宿主机目录与容器目录的绑定(挂载)。/bit/shardingsphere/proxy/conf:/opt/shardingsphere-proxy/conf
:将宿主机上的/bit/shardingsphere/proxy/conf
目录挂载到容器内的/opt/shardingsphere-proxy/conf
目录。这个目录通常存放 ShardingSphere-Proxy 的核心配置文件,如server.yaml
(服务配置)和config-xxx.yaml
(数据分片/读写分离规则配置)。通过挂载,你可以在宿主机上直接修改配置文件,而无需进入容器内部,修改后重启容器即可生效。
-v /bit/shardingsphere/proxy/ext-lib:/opt/shardingsphere-proxy/ext-lib \
/bit/shardingsphere/proxy/ext-lib:/opt/shardingsphere-proxy/ext-lib
:将宿主机上的/bit/shardingsphere/proxy/ext-lib
目录挂载到容器内的/opt/shardingsphere-proxy/ext-lib
目录。这个目录用于存放用户自定义的 JAR 包,例如:- 数据库驱动(如果官方镜像里没有你需要的数据库驱动,如 Oracle)。
- 自定义的分片算法类。
- 自定义的加密算法类。
- 自定义的分布式序列生成器。
-v /bit/shardingsphere/proxy/logs:/opt/shardingsphere-proxy/logs \
/bit/shardingsphere/proxy/logs:/opt/shardingsphere-proxy/logs
:将宿主机上的/bit/shardingsphere/proxy/logs
目录挂载到容器内的/opt/shardingsphere-proxy/logs
目录。ShardingSphere-Proxy 的运行日志(如shardingsphere.log
)会输出到这个目录。挂载后,你可以在宿主机上方便地查看和管理日志文件,即使容器被删除,日志也保留在宿主机上。
-e JVM_OPTS="-Xms256m -Xmx256m -Xmn128m" \
-e
:用于设置环境变量(Environment Variable)。JVM_OPTS="-Xms256m -Xmx256m -Xmn128m"
:将环境变量JVM_OPTS
的值设置为指定的 JVM 内存参数。当 ShardingSphere-Proxy 启动时,会读取这个环境变量,并将其作为 JVM 的启动参数,从而控制其内存使用。
--name ss-proxy \
--name
:为启动的容器指定一个名称。ss-proxy
:这个容器的名称被设置为ss-proxy
。之后你可以使用docker stop ss-proxy
,docker start ss-proxy
,docker logs ss-proxy
等命令来管理这个容器,比使用容器的 ID 更方便。
apache/shardingsphere-proxy:5.3.2
apache/shardingsphere-proxy:5.3.2
:这是要运行的 Docker 镜像的名称和标签。apache/shardingsphere-proxy
是镜像的名称。5.3.2
是镜像的版本标签(Tag)。Docker 会首先检查本地是否有这个镜像,如果没有,会自动从 Docker Hub(或其他配置的镜像仓库)拉取这个版本的镜像,然后使用它来创建并启动容器。
废话不多说,我们直接执行就OK。
然后我们来看看有没有启动成功啊
# 查看是否启动成功
docker ps -a
我们发现没有启动成功,这个是因为docker容器没有进行配置
修改配置文件
首先我们需要来到这个配置文件对应的 宿主机映射目录里面,也就是/bit/shardingsphere/proxy/conf
cd /bit/shardingsphere/proxy/conf
我们就在这个目录里面创建一个配置文件server.yaml
vim server.yaml
然后把下面这个粘贴进去即可
# 运行模式配置
mode:
type: Standalone # 单机模式(非集群部署)
# 用户权限配置
authority:
users:
- user: root@% # 用户名格式:用户名@访问主机(%表示允许任意主机访问)
password: 123456 # 用户密码
privilege:
type: ALL_PERMITTED # 权限类型:授予该用户所有操作权限
# 系统属性配置
props:
sql-show: true # 是否在日志中显示执行的SQL语句(true=显示,false=隐藏)
proxy-mysql-default-version: 8.0.42 # 代理对外暴露的MySQL默认版本号
我们保存退出。
上传MySQL驱动
连接MySQL数据库时,需要把MySQL驱动包放入宿主机映射扩展目录/bit/shardingsphere/proxy/ext-lib中
cd /bit/shardingsphere/proxy/ext-lib
现在我们可以上一小节下载的 MySQL驱动驱动直接复制到这里即可。
当然,大家执行下面这个命令也是可以的
wget https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar
启动
启动Docker容器,运行ShardingSphere
# 重新启动容器
docker start ss-proxy
# 查看状态,启动成功
docker ps
没有一点问题
测试连接
通过客户端工具连接
# 指定主机和端口号进行连接,成功
mysql -uroot -p -h127.0.0.1 -P3307
进入Docker容器
# 进入容器
docker exec -it ss-proxy env LANG=C.UTF-8 /bin/bash
如果我们想要远程登陆这个服务器的话,需要去云服务器里面的防火墙和安全组里面开放这个3307端口。
二. 实践:水平分片
2.1 应用场景
随着系统持续运行,各业务模块积累的数据量不断攀升。在垂直分库架构下,虽然不同业务的表分散在不同数据库中,但单个数据表内的记录仍持续增长。当表数据量超过数千万行时,底层存储引擎使用的B+树索引层级会逐渐加深。树高每增加一层,数据查询就需要多一次磁盘I/O操作,导致查询延迟显著上升。此时,垂直分库已无法解决单表数据膨胀带来的性能衰减。
为突破这一瓶颈,需要引入水平分片机制。其核心思路是将单张逻辑表的数据,按预设规则(如用户ID哈希、时间范围等)横向拆分到多个结构相同的物理分片表中。每个分片表只保存原表的部分数据子集,从而将海量数据分散存储。
这种拆分带来两个关键优势:首先,单个分片表的数据规模大幅缩减,有效控制了B+树的高度。当树高稳定在3层以内时,查询操作通常只需1-3次磁盘I/O,响应时间保持在毫秒级。其次,不同分片表可部署在不同物理节点上,查询负载被天然分散。用户请求会根据分片规则自动路由到特定分片,避免单一节点过载。
更重要的是,水平分片使系统获得弹性扩展能力。当数据持续增长时,只需增加新的分片节点即可线性提升存储容量和吞吐量,无需重构整体架构。这种设计既保障了海量数据场景下的稳定性能,也为后续业务扩展预留了技术空间。
2.2 架构图
在前面垂直分库的时候,我们是采用下面这个架构图
- server-user服务器中存储t_user表中的数据
- server-order服务器中存储t_order表中的数据
然而随着业务的发展,server-order 节点中 t_order 表的存储压力不断增大,这时就需要对 t_order 表进行横向拆分,也就是水平分片。我们先采用水平分库,将t_order表里面的数据拆分成torder0和t_order1两部分,分别存放到server_order0和server_order1两台机器上
然而server-order 节点中 t_order 表的存储压力成指数级增长,上图的水平分库已经不能满足需求了,我们需要在水平分库的基础之上再将torder0和t_order1进行水平分片。
分库策略:为了让请求正确路由到指定的数据库中
分表策略:为了让请求在正确的分片表中操作数据
2.3 服务器规划
由原来的一台服务器中的一张表,变成两台服务器中的四张表,分担数据存储压力
- 订单服务器:容器名 server-order0,端口号:63310
- 订单服务器:容器名 server-order1,端口号:63311
2.4 创建server-user容器
创建Docker容器
docker run -d \
-p 53310:3306 \
-v /bit/mysql/user/conf:/etc/mysql/conf.d \
-v /bit/mysql/user/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-user \
mysql:8.0.42
关键参数解释:
-d
:后台运行容器(detached 模式)。-p 53310:3306
:将主机的 53310 端口映射到容器的 3306 端口(MySQL 默认端口),外部通过主机IP:53310
访问数据库。-v /bit/mysql/user/conf:/etc/mysql/conf.d
:挂载本地配置目录到容器,用于自定义 MySQL 配置(如my.cnf
)。-v /bit/mysql/user/mysql:/var/lib/mysql
:挂载本地数据目录到容器,实现数据持久化(防止容器删除后数据丢失)。-e MYSQL_ROOT_PASSWORD=123456
:设置 MySQL 的root
用户密码。--name server-user
:为容器指定名称(便于后续管理)。mysql:8.0.42
:使用的 MySQL 镜像版本。
# 查看容器状态
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 4 seconds ago Up 3 seconds 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
进入Docker容器
# 进入Docker容器,env LANG=C.UTF-8避免中文乱码问题
docker exec -it server-user env LANG=C.UTF-8 /bin/bash
# 运行Mysql客户端
mysql -uroot -p
# 修改root用户密码
SET PASSWORD = '123456';
创建数据库
使用客户端登录数据库并执行SQL
-- 创建数据库
CREATE DATABASE IF NOT EXISTS user_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
-- 使用 user_db 数据库作为当前操作上下文
USE user_db;
CREATE TABLE IF NOT EXISTS t_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
测试远程登陆
接下来我们将测试一下远程登陆
注意:如果你的宿主机是云服务器的话,请记得去云服务器官网开放防火墙端口和设置安全组
mysql -h150.158.139.86 -P53310 -uroot -p
2.5 创建server-order0和server-order1容器
创建Docker容器
创建两台Docker,容器名端口号分别为 server-order0:63310 和 server-order1:63311
# 创建server-order0,注意修改相应的容器名、端口号和映射路径
docker run -d \
-p 63310:3306 \
-v /bit/mysql/order0/conf:/etc/mysql/conf.d \
-v /bit/mysql/order0/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order0 \
--restart always \
mysql:8.0.42
# 创建server-order1,注意修改相应的容器名、端口号和映射路径
docker run -d \
-p 63311:3306 \
-v /bit/mysql/order1/conf:/etc/mysql/conf.d \
-v /bit/mysql/order1/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
--restart always \
mysql:8.0.42
查看是否启动成功
docker ps
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 2 seconds ago Up 1 second 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 3 minutes ago Up 3 second 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
接下来我们将使用客户端分别登录 server-order0 和 server-order1 并创建数据库
水平分片后由于数据分布在不同的数据节点,所以主键值不能依赖自增值,生成策略由业务层实现,插入记录时指定一个主键值。
进入Docker容器 server-order0,创建t_order1和
# 进入Docker容器,env LANG=C.UTF-8避免中文乱码问题
docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash
# 运行Mysql客户端
mysql -uroot -p
# 修改root用户密码
SET PASSWORD = '123456';
然后我们需要创建torder0和torder1的表的结构
-- 创建订单数据库(使用UTF8字符集和现代排序规则)
CREATE DATABASE IF NOT EXISTS order_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
-- 切换到订单数据库
USE order_db;
-- 创建分片表0(用于存储用户ID为偶数的订单)
CREATE TABLE IF NOT EXISTS t_order0 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
-- 创建分片表1(用于存储用户ID为奇数的订单)
CREATE TABLE IF NOT EXISTS t_order1 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
进入Docker容器 server-order1,创建对应的表
# 进入Docker容器,env LANG=C.UTF-8避免中文乱码问题
docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash
# 运行Mysql客户端
mysql -uroot -p
# 修改root用户密码
SET PASSWORD = '123456';
然后我们需要创建torder0和torder1的表的结构
-- 创建订单数据库(使用UTF8字符集和现代排序规则)
CREATE DATABASE IF NOT EXISTS order_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
-- 切换到订单数据库
USE order_db;
-- 创建分片表0(用于存储用户ID为偶数的订单)
CREATE TABLE IF NOT EXISTS t_order0 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
-- 创建分片表1(用于存储用户ID为奇数的订单)
CREATE TABLE IF NOT EXISTS t_order1 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
测试远程连接
接下来我们将测试一下远程连接
注意:如果你的宿主机是云服务器的话,那么就一定要记得去云服务器官网开放防火墙端口号并且去开放安全组端口号
mysql -h150.158.139.86 -P63310 -uroot -p
mysql -h150.158.139.86 -P63311 -uroot -p
2.6.日志配置
首先我们需要来到这个配置文件对应的 宿主机映射目录里面,也就是/bit/shardingsphere/proxy/conf
cd /bit/shardingsphere/proxy/conf
接下来我们将创建一个文件
vim logback.xml
并编写以下内容
<?xml version="1.0"?>
<configuration>
<!-- 日志输入到文件 -->
<appender name="SHARDING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志路径 -->
<file>./logs/shardingsphere.log</file>
<encoder>
<!-- 日志输入的样式 -->
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>shardingsphere.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="SHARDING_FILE" />
</root>
</configuration>
我们保存退出一下 。
2.7 数据节点配置
数据节点指的是某个数据源下的某张具体的分片表
数据节点配置与可以称为标准分片表配置
数据分片需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
配置数据源
配置 server_user、server_order0、server_order1 三个数据源
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
配置数据节点
每个 t_order 表都是一个数据节点,当前 t_order 表分布如下:
- server_order0.t_order0
- server_order0.t_order1
- server_order1.t_order0
- server_order1.t_order1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order0.t_order0,server_order0.t_order1,server_order1.t_order0,server_order1.t_order1
我们在想 :server_order0.t_order0,server_order0.t_order1,server_order1.t_order0,server_order1.t_order1能不能再简化一下,我们可以去ShardingSphere官网看看核心概念 :: ShardingSphere
我们现在就去修改rules节点,使用行表达式对 t_order 表进行水平分库配置
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
所有对逻辑表 t_user
的操作直接路由到 server_user
数据源(即 user_db
数据库)
所有对逻辑表t_order的操作路由到下面四个节点
- server_order0.t_order0
- server_order0.t_order1
- server_order1.t_order0
- server_order1.t_order1
完整配置文件内容
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
我们把这个内容写入/bit/shardingsphere/proxy/conf/config-sharding.yaml.
保存退出
2.8.测试数据节点
我们在上面的配置文件里面写了
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
每个 t_order 表都是一个数据节点,当前 t_order 表分布如下:
- server_order0.t_order0
- server_order0.t_order1
- server_order1.t_order0
- server_order1.t_order1
事实上呢?
我们一般是不会直接像上面2.7里面的配置文件那样子直接写actualDataNodes: server_order${0..1}.t_order${0..1}的,我们得先一个一个数据节点进行测试,等每个数据节点都测试成功了,我们再写actualDataNodes: server_order${0..1}.t_order0${0..1}的
2.8.1.测试server_order0.t_order0节点
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把我们把config-sharding.yaml里面的一部分内容修改成下面这样子
……
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order0.t_order0
然后保存退出 ,我们重启Docker容器
docker restart ss-proxy
docker ps
运行结果如下
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 15 hours ago Up 15 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 16 hours ago Up 16 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 2 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 41 hours ago Up 6 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
接着我们打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆
mysql -h127.0.0.1 -P3307 -uroot -p
我们接着执行下面这个
注意:如果显示的是下面这个情况,不要太慌张,这个是ShardingSphere-Proxy5.3.2的bug,不影响我们的!!
然后我们执行下面这个语句
-- 插入记录并在server_order0.t_order0节点中查看是否插入成功
insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00);
我们看看日志中的结果
[INFO ] 2025-08-01 08:26:34.003 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
[INFO ] 2025-08-01 08:26:34.003 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
可见这条插入到逻辑表t_order的数据插入到server_order0里的t_order0表了。与我们配置的一模一样。
2.8.2.测试server_order0.t_order1节点
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把我们把config-sharding.yaml里面的一部分内容修改成下面这样子
……
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order0.t_order1
然后保存退出 ,我们重启Docker容器
docker restart ss-proxy
docker ps
运行结果如下
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 25 hours ago Up 4 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 43 hours ago Up 3 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
接着我们打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆
mysql -h127.0.0.1 -P3307 -uroot -p
我们接着执行下面这个
注意:如果显示的是下面这个情况,不要太慌张,这个是ShardingSphere-Proxy5.3.2的bug,不影响我们的!!
然后我们执行下面这个语句
-- 插入记录并在server_order0.t_order1节点中查看是否插入成功
insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00);
我们看看日志中的结果
[INFO ] 2025-08-01 08:34:50.284 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00)
[INFO ] 2025-08-01 08:34:50.284 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order1 (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00)
可见这条插入到逻辑表t_order的数据插入到server_order0里的t_order1表了。与我们配置的一模一样。
2.8.3.测试server_order1.t_order0节点
由于此时还没有配置分片规则,所以先单独测试每个数据节点是正常工作,修改配置文件并重启
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把我们把config-sharding.yaml里面的一部分内容修改成下面这样子
……
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order1.t_order0
然后保存退出 ,我们重启Docker容器
docker restart ss-proxy
docker ps
运行结果如下
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 25 hours ago Up 4 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 43 hours ago Up 8 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
接着我们打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆
mysql -h127.0.0.1 -P3307 -uroot -p
我们接着执行下面这个
注意:如果显示的是下面这个情况,不要太慌张,这个是ShardingSphere-Proxy5.3.2的bug,不影响我们的!!
然后我们执行下面这个语句
-- 插入记录并在server_order1.t_order0节点中查看是否插入成功
insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00);
我们看看日志中的结果
[INFO ] 2025-08-01 08:38:10.453 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00)
[INFO ] 2025-08-01 08:38:10.454 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00)
可见这条插入到逻辑表t_order的数据插入到server_order1里的t_order0表了。与我们配置的一模一样。
2.8.4.测试server_order1.t_order1节点
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把我们把config-sharding.yaml里面的一部分内容修改成下面这样子
……
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order1.t_order1
然后保存退出 ,我们重启Docker容器
docker restart ss-proxy
docker ps
运行结果如下
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 25 hours ago Up 4 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 43 hours ago Up 5 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
接着我们打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆
mysql -h127.0.0.1 -P3307 -uroot -p
注意:如果显示的是下面这个情况,不要太慌张,这个是ShardingSphere-Proxy5.3.2的bug,不影响我们的!!
然后我们执行下面这个语句
-- 插入记录并在server_order1.t_order1节点中查看是否插入成功
insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00);
我们看看日志中的结果
[INFO ] 2025-08-01 08:52:16.198 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00)
[INFO ] 2025-08-01 08:52:16.198 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order1 (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00)
可见这条插入到逻辑表t_order的数据插入到server_order1里的t_order1表了。与我们配置的一模一样。
2.8.5.完整配置
现在四个数据节点我们都测试完成了,都没有问题。现在我们就可以放心的去配置我们的
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
然后把下面这个内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 18 hours ago Up 18 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 25 hours ago Up 4 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 44 hours ago Up 5 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
我们登陆去看看
mysql -h127.0.0.1 -P3307 -uroot -p
这个时候ShardingSphere-Proxy5.3.2的bug就不见了!!
那么到现在,我们就实现了逻辑表t_order的数据会从下面四个表中获取
- server_order0.t_order0
- server_order0.t_order1
- server_order1.t_order0
- server_order1.t_order1
但是也就仅此而已,我们还需要做更多的配置。
2.6 水平分库配置
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
思考配置思路
还记得我们创建的t_order0和t_order1表的结构吗?其实是一模一样的的。
-- 创建分片表0(用于存储用户ID为偶数的订单)
CREATE TABLE IF NOT EXISTS t_order0 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
-- 创建分片表1(用于存储用户ID为奇数的订单)
CREATE TABLE IF NOT EXISTS t_order1 (
id BIGINT PRIMARY KEY, -- 主键ID
order_no VARCHAR(30) COMMENT '订单号', -- 订单唯一编号
amount DECIMAL(12, 2) COMMENT '订单金额', -- 订单金额(最大999亿)
user_id BIGINT COMMENT '用户编号' -- 关联用户ID
);
我们这就根据t_order0和t_order1中的user_id 来确定数据写入下面哪个数据节点 。
该层的核心目标是将数据分散存储到不同的物理数据库节点(server_order0
和 server_order1
),以提升系统的整体存储容量、处理能力和并发性能。
具体路由策略是:系统根据订单数据中的 user_id
字段值来决定其所属的数据库节点。
- 当
user_id
为 偶数 时(也就是user_id%2==0时)
,数据将被路由至server_order0
节点。 - 当
user_id
为 奇数 时(也就是user_id%2!=0时)
,数据将被路由至server_order1
节点。
这种按用户 ID 奇偶性分库的策略具有显著优势:它有效避免了跨库事务的复杂性(因为同一用户相关的订单操作通常都落在同一个数据库节点内完成),同时大幅减少了跨库查询的需求(查询特定用户的订单数据只需访问其对应的单个数据库节点即可)。
配置分库策略
那怎么实现呢?大家可以去官网看看:数据分片 :: ShardingSphere
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order0${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_db_inline_userid # 分片算法名称(自定义)
2.6.1.行表达式分片算法
什么是行表达式分片算法
咱们用大白话解释一下 Apache ShardingSphere 的行表达式分片算法:
它是什么?
想象一下你有一大堆订单数据(比如存在 t_order
表里),单张表装不下或者查询太慢了。你需要把它们拆分到不同的物理表(比如 t_order_0
, t_order_1
, ... t_order_7
)或者不同的数据库里。
行表达式分片算法就是一种超级简单、写一行“公式”就能告诉 ShardingSphere 怎么拆分数据的方法。
核心思想:写个“分片公式”
-
不用写代码: 你不需要为了分片去写复杂的 Java 程序。ShardingSphere 内置了这个功能。
-
写个表达式: 你只需要写一个类似数学公式或简单规则的字符串(就是
algorithm-expression
属性)。 -
基于分片键: 这个公式是基于你选定的分片键(比如
user_id
或order_id
)来计算的。这个算法只支持一个分片键。 -
支持 = 和 IN: 它最适合处理精确匹配的查询,比如
WHERE user_id = 123
或者WHERE order_id IN (1001, 1002, 1003)
。
举个最经典的例子 🌰:
-
场景: 你想把用户表
t_user
分成 8 张物理表,根据用户 ID (u_id
) 的尾数来决定数据存在哪张表。 -
表达式:
t_user_$->{u_id % 8}
-
怎么理解:
-
t_user_
: 这是你表名的前缀。 -
$->{ ... }
: 这是行表达式的固定语法,告诉 ShardingSphere 里面是计算规则。 -
u_id % 8
: 这就是“分片公式”。它计算u_id
除以 8 的余数。-
如果
u_id
是 10,10 % 8 = 2
,那么这条数据就存到t_user_2
表。 -
如果
u_id
是 25,25 % 8 = 1
,那么这条数据就存到t_user_1
表。 -
如果
u_id
是 32,32 % 8 = 0
,那么这条数据就存到t_user_0
表。
-
-
-
结果: 数据会根据
u_id
均匀(理想情况下)分布到t_user_0
到t_user_7
这 8 张物理表中。
如果大家想要了解更多,可以去官网看看:分片算法 :: ShardingSphere
好,我们现在就根据行表达式算法来对我们的配置文件进行修改
订单表中 user_id % 2 来确定当前记录写入哪个数据库
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order0${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_db_inline_userid # 分片算法名称(自定义)
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 前面定义的分片算法名称
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
完整配置文件
/bit/shardingsphere/proxy/conf/config-sharding.yaml里面的内容应该是
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order0${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_db_inline_userid # 分片算法名称(自定义)
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 前面定义的分片算法名称
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
写入测试
我们登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
执行INSERT语句,向订单表中插入一条数据
insert into t_order (id, order_no, user_id, amount) values (5, 'BIT005', 1, 20.00);
插入数据后报如下错误
mysql> insert into t_order (id, order_no, user_id, amount) values (5, 'BIT005', 1, 20.00);
ERROR 20087 (44000): Please check your sharding conditions `ShardingConditions(conditions=[ShardingCondition(values=[t_order.user_id = 1], startIndex=0)], sqlStatementContext=org.apache.shardingsphere.infra.binder.statement.dml.InsertStatementContext@57de437d, rule=org.apache.shardingsphere.sharding.rule.ShardingRule@20999517, subqueryContainsShardingCondition=true)` to avoid same record in table `t_order` routing to multiple data nodes.
这个意思就是mysql防止t_order表中的同一记录路由到多个数据节点
报错的原因是当前虽然根据 user_id 指定了分库策略,但是没有指定分表策略,也就是说当前不知道写入数据库中的 t_order0 表还是 t_order1 表
我们回去修改配置文件中数据节点配置,按照下面进行更改
完整的配置文件
/bit/shardingsphere/proxy/conf/config-sharding.yaml里面的内容应该是
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order0
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_db_inline_userid # 分片算法名称(自定义)
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 前面定义的分片算法名称
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
执行INSERT语句
-- 每条记录的用户Id不同
insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00);
insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 2, 20.00);
insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 3, 20.00);
insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 4, 20.00);
查看日志
# 用户id=1 路由到server_order1
[INFO ] 2025-08-01 13:00:09.240 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
[INFO ] 2025-08-01 13:00:09.241 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
# 用户id=2 路由到server_order0
[INFO ] 2025-08-01 13:00:16.759 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 2, 20.00)
[INFO ] 2025-08-01 13:00:16.759 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (2, 'BIT002', 2, 20.00)
# 用户id=3 路由到server_order1
[INFO ] 2025-08-01 13:00:23.936 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 3, 20.00)
[INFO ] 2025-08-01 13:00:23.937 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (3, 'BIT003', 3, 20.00)
# 用户Id=4 路由到server_order0
[INFO ] 2025-08-01 13:01:05.492 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 4, 20.00)
[INFO ] 2025-08-01 13:01:05.492 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (4, 'BIT004', 4, 20.00)
很好啊,完全实现了我们需要的功能。
2.6.2.取模分片算法
其实我们不止可以使用行表达式来实现这个功能,我们也可以通过取模分片算法来实现
分片算法参考官方文档:分片算法 :: ShardingSphere
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order0
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
执行INSERT语句
-- 每条记录的用户Id不同
insert into t_order (id, order_no, user_id, amount) values (5, 'BIT001', 5, 20.00);
insert into t_order (id, order_no, user_id, amount) values (6, 'BIT002', 6, 20.00);
insert into t_order (id, order_no, user_id, amount) values (7, 'BIT003', 7, 20.00);
insert into t_order (id, order_no, user_id, amount) values (8, 'BIT004', 8, 20.00);
执行完上面四句,日志里面就出现了下面这些
# 用户id=5 路由到server_order1
[INFO ] 2025-08-01 13:49:09.192 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (5, 'BIT001', 5, 20.00)
[INFO ] 2025-08-01 13:49:09.193 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (5, 'BIT001', 5, 20.00)
# 用户id=6 路由到server_order0
[INFO ] 2025-08-01 13:49:18.316 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (6, 'BIT002', 6, 20.00)
[INFO ] 2025-08-01 13:49:18.317 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (6, 'BIT002', 6, 20.00)
# 用户id=7 路由到server_order1
[INFO ] 2025-08-01 13:49:26.735 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (7, 'BIT003', 7, 20.00)
[INFO ] 2025-08-01 13:49:26.736 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (7, 'BIT003', 7, 20.00)
# 用户id=8 路由到server_order0
[INFO ] 2025-08-01 13:49:33.587 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (8, 'BIT004', 8, 20.00)
[INFO ] 2025-08-01 13:49:33.587 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (8, 'BIT004', 8, 20.00)
与行表达式的效果一致。
2.7 水平分表配置
2.7.1.配置分表策略
我们在上面就单单只是配置了下面这个数据源
然后我们插入数据后报如下错误
mysql> insert into t_order (id, order_no, user_id, amount) values (5, 'BIT005', 1, 20.00);
ERROR 20087 (44000): Please check your sharding conditions `ShardingConditions(conditions=[ShardingCondition(values=[t_order.user_id = 1], startIndex=0)], sqlStatementContext=org.apache.shardingsphere.infra.binder.statement.dml.InsertStatementContext@57de437d, rule=org.apache.shardingsphere.sharding.rule.ShardingRule@20999517, subqueryContainsShardingCondition=true)` to avoid same record in table `t_order` routing to multiple data nodes.
报错的原因是当前虽然根据 user_id 指定了分库策略,但是没有指定分表策略,也就是说当前不知道写入数据库中的 t_order0 表还是 t_order1 表
现在我们就来配置一下这个到底是写入到哪一个表里面呢?
我们现在就根据插入的数据里面中的 order_no字段 来确定数据写入t_order0和t_order1表哪个数据节点
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把下面的内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
注意我修改了下面这些字段
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
然后往我们执行下面这个
-- 插入一条数据
insert into t_order (id, order_no, user_id, amount) values (9, 'BIT009', 9, 20.00);
得到如下错误信息
30000 - Unknown exception: For input string: "BIT009"
这个是因为配置文件中指定的数据分片算法类型是 MOD,但是订单号是一个字符串,对字符串取模会报错
2.7.2 HASH取模分片算法
修改配置文件添加HASH取模分片算法
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称(自定义)——和下面的alg_hash_mod对应
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD # 分片算法类型
props:
sharding-count: 2 # 分片数量,表示根据分片列对2取模
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
2.7.3.测试
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
然后往我们执行下面这个
清空之前的所有数据
TRUNCATE TABLE t_order;
插入测试1:执行INSERT语句,user_id = 1
-- 每条记录的user_id相同,order_no不同
insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00);
insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00);
insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00);
insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00);
-- 订单号对应的HASHCODE
-- BIT001 -> 1959491108
-- BIT002 -> 1959491109
-- BIT003 -> 1959491110
-- BIT004 -> 1959491111
查看日志,发现多了下面这些
# BIT001的记录被路由到了server_order1 里的t_order0 表中
[INFO ] 2025-08-02 01:48:12.377 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
[INFO ] 2025-08-02 01:48:12.377 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (1, 'BIT001', 1, 20.00)
# BIT002的记录被路由到了server_order1 里的t_order1 表中
[INFO ] 2025-08-02 01:48:21.181 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00)
[INFO ] 2025-08-02 01:48:21.181 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order1 (id, order_no, user_id, amount) values (2, 'BIT002', 1, 20.00)
# BIT003的记录被路由到了server_order1 里的t_order0 表中
[INFO ] 2025-08-02 01:48:29.177 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00)
[INFO ] 2025-08-02 01:48:29.177 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (id, order_no, user_id, amount) values (3, 'BIT003', 1, 20.00)
# BIT004的记录被路由到了server_order1 里的t_order1 表中
[INFO ] 2025-08-02 01:48:37.805 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00)
[INFO ] 2025-08-02 01:48:37.805 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order1 (id, order_no, user_id, amount) values (4, 'BIT004', 1, 20.00)
插入测试2:执行INSERT语句,user_id = 2
-- 每条记录的用户Id相同,订单号不同
insert into t_order (id, order_no, user_id, amount) values (5, 'BIT005', 2, 20.00);
insert into t_order (id, order_no, user_id, amount) values (6, 'BIT006', 2, 20.00);
insert into t_order (id, order_no, user_id, amount) values (7, 'BIT007', 2, 20.00);
insert into t_order (id, order_no, user_id, amount) values (8, 'BIT008', 2, 20.00);
-- 订单号对应的HASHCODE
-- BIT006 -> 1959491112
-- BIT006 -> 1959491113
-- BIT007 -> 1959491114
-- BIT008 -> 1959491115
我们查看一下日志,发现多下面这个
# BIT005的记录被路由到了server_order0 里的t_order0 表中
[INFO ] 2025-08-02 01:53:32.014 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (5, 'BIT005', 2, 20.00)
[INFO ] 2025-08-02 01:53:32.014 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (5, 'BIT005', 2, 20.00)
# BIT006的记录被路由到了server_order0 里的t_order1 表中
[INFO ] 2025-08-02 01:53:41.512 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (6, 'BIT006', 2, 20.00)
[INFO ] 2025-08-02 01:53:41.512 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order1 (id, order_no, user_id, amount) values (6, 'BIT006', 2, 20.00)
# BIT007的记录被路由到了server_order0 里的t_order0 表中
[INFO ] 2025-08-02 01:53:49.294 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (7, 'BIT007', 2, 20.00)
[INFO ] 2025-08-02 01:53:49.294 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (id, order_no, user_id, amount) values (7, 'BIT007', 2, 20.00)
# BIT008的记录被路由到了server_order0 里的t_order1 表中
[INFO ] 2025-08-02 01:53:56.474 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (id, order_no, user_id, amount) values (8, 'BIT008', 2, 20.00)
[INFO ] 2025-08-02 01:53:56.475 [ShardingSphere-Command-4] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order1 (id, order_no, user_id, amount) values (8, 'BIT008', 2, 20.00)
很完美啊!!
查询测试1:查询所有内容
查询t_order表所有内容
-- 查询所有订单记录
select * from t_order;
查看日志
# 逻辑SQL
[INFO ] 2025-08-02 01:59:52.401 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Logic SQL: select * from t_order
# 实际SQL,查询了所有的数据节点
[INFO ] 2025-08-02 01:59:52.402 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order0 UNION ALL select * from t_order1
[INFO ] 2025-08-02 01:59:52.402 [ShardingSphere-Command-5] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order0 UNION ALL select * from t_order1
可以看出ShardinSphere在所有的数据节点查询记录,并把各个节点的记录组装好,统一返回给客户端
查询测试2:条件查询
-- 查询指定用户的订单
select * from t_order where user_id = 1;
查看日志
# 逻辑SQL
[INFO ] 2025-08-02 02:01:10.337 [ShardingSphere-Command-6] INFO ShardingSphere-SQL - Logic SQL: select * from t_order where user_id = 1
# 实际SQL,查询部分数据节点
[INFO ] 2025-08-02 02:01:10.338 [ShardingSphere-Command-6] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order0 where user_id = 1 UNION ALL select * from t_order1 where user_id = 1
-- 查询指定用户的订单
select * from t_order where user_id = 2;
查看日志
# 逻辑SQL
[INFO ] 2025-08-02 02:02:23.620 [ShardingSphere-Command-7] INFO ShardingSphere-SQL - Logic SQL: select * from t_order where user_id = 2
# 实际SQL,查询部分数据节点
[INFO ] 2025-08-02 02:02:23.620 [ShardingSphere-Command-7] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order0 where user_id = 2 UNION ALL select * from t_order1 where user_id = 2
根据分片字段进行查询时ShardinSphere根据分片规则访问记录所有的数据节点,并返回结果
分片时应使用经常查询的字段进行分片。
2.8 分布式序列算法
2.8.1.分布式环境下主键的自动生成存在的问题
在传统单机数据库的软件开发中,主键的自动生成是一项基础且关键的需求。主流数据库系统为此提供了成熟的内置支持机制,例如 MySQL 的 AUTO_INCREMENT
自增列和 Oracle 的序列 (SEQUENCE
)。这些机制在单一数据库实例内能够高效、可靠地保证主键的唯一性和连续性。
然而,当数据量激增需要进行水平分片(Sharding) 时,确保全局唯一主键的生成就变得极具挑战性。在分片架构下,原本单一的逻辑表的数据会被分散存储在多个物理分片(数据节点) 的不同实际表中。问题在于,这些位于不同分片上的实际表,其内置的自增机制(如 MySQL 的 AUTO_INCREMENT
或类似功能)是彼此独立、互不感知的。每个分片只在其自身的范围内维护自增序列,无法得知其他分片的当前值。这必然导致一个严重后果:不同分片上的实际表会生成完全相同的自增值作为主键,从而破坏了全局唯一性约束。
这么说还是有点不太直观,完美看个例子
想象一所学校(相当于整个数据库系统):
-
单班教学(传统单机数据库):
-
学校只有一个班级(一个数据库实例)。
-
老师(数据库的自增机制,如 MySQL
AUTO_INCREMENT
)负责给班里的每个新学生(新记录)分配一个唯一的学号(主键)。 -
老师手里有一份名单,记录着上一个学号是多少(比如 50),新学生来了就顺延(51,52,53...)。非常简单,保证学号在班里绝不重复。
-
-
学生太多,需要分班(数据分片/Sharding):
-
学校学生暴涨(数据量激增),一个班装不下了。
-
学校决定分成 3 个平行班:一班、二班、三班(3个物理分片/数据节点)。每个班都有一份学生名单(实际表),记录自己班的学生。
-
学校依然需要保证全校范围内每个学生的学号都是唯一的(全局唯一主键)。
-
-
独立班主任的问题(分片独立的自增机制):
-
每个班都安排了一位班主任(分片上的自增机制)。
-
学校没有统一协调学号的办公室。
-
一班班主任: 自己手里有一份名单,从 1 开始给学生编号(1, 2, 3...)。
-
二班班主任: 同样,自己手里也有一份名单,也从 1 开始给学生编号(1, 2, 3...)。
-
三班班主任: 同样如此(1, 2, 3...)。
-
-
学号冲突(主键重复):
-
现在,一班有个学生学号是 1(张三),二班也有个学生学号是 1(李四),三班也有个学生学号是 1(王五)。
-
问题来了: 当学校需要把所有班级的学生名单合并起来看(查询逻辑表数据)时,系统会发现有三个学号为 1 的学生!这严重违反了“全校唯一学号”的规定(破坏了主键的全局唯一性)。
-
即使班主任们各自在班内编号是连续的、不重复的(如一班:1,2,3;二班:1,2,3;三班:1,2,3),但放到全校范围看,学号 1 重复了三次,学号 2、3 等等也都重复了。
-
这个例子如何对应技术概念:
-
学校: 整个数据库系统 / 逻辑表。
-
班级: 物理分片 / 数据节点 / 实际表。
-
班主任: 分片上独立运行的数据库自增机制(如 MySQL
AUTO_INCREMENT
)。 -
班主任手里的独立名单: 每个分片/实际表维护的独立的、互不感知的自增序列。
-
学号: 主键值。
-
学号冲突: 不同分片上的实际表生成了相同的 ID,导致全局主键重复。
-
“全校唯一学号”规定: 数据库主键必须全局唯一的约束。
核心难点:
就像班主任们不知道其他班用了哪些学号一样,分片一上的 MySQL 不知道分片二和分片三下一个自增 ID 是什么。它们各自在本地计数,必然会导致全局范围内的 ID 重复。
结论:
这就是为什么在分片数据库环境下,不能直接依赖数据库原生的单机自增机制(如 MySQL AUTO_INCREMENT
或 Oracle SEQUENCE
)来生成主键。需要引入分布式 ID 生成方案(如 Snowflake 算法、UUID、基于数据库号段的分配、中心化的 ID 生成服务等)来解决班主任们(各分片)之间缺乏协调的问题,确保全校(全局)学号(主键)的唯一性。
目前有许多第三方解决方案可以完美解决这个问题,如 UUID 等依靠特定算法自生成不重复键,或者通过引入主键生成服务等。为了方便用户使用、满足不同用户不同使用场景的需求,Apache ShardingSphere 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。
2.8.2 UUID
UUID是ShardingSphere内置的分布式序列算法之一。
可以生成时间、空间上都独一无二的值,但是由于UUID是无序的,不太适合作为数据库的主键
2.8.3 雪花算法
简介
ShardingSphere在分片规则配置模块可配置每个表的主键生成策略,默认使用雪花算法(snowflake)生成64bit的长整型数据。
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。
实现原理
核心目标: 在分布式系统(很多台机器一起干活)中,给每一条数据生成一个全局唯一且大体有序的ID(主键)。
想象一个超大的学校(分布式系统)需要给学生发学号(主键):
-
传统问题: 如果让每个班级(数据库分片)的班主任(数据库自增)自己发学号,就会出现多个班都有“学号1”的情况,全校范围就重复了。
-
雪花算法解决方案: 学校设立了一个智能学号生成中心(雪花算法),统一负责给全校所有学生发学号。这个中心发学号的规则非常精密:
学号的构成(64位二进制,最终转成一个长整数):
这个学号(ID)就像一张信息卡片,由4个关键部分组成:
-
第1部分:预留位 (1 bit) - “国籍标识”
-
恒定为0(正数)。就像标识这个学号是“本校”的,暂时没用,先占个位置。
-
-
第2部分:时间戳 (41 bits) - “入学年份+精确到毫秒的报到时间”
-
这是最重要的部分! 它记录了学生报到(ID生成)的精确时间(毫秒级)。
-
能表示的时间范围超级长:大约69年(从2016年11月1日开始算,能用到2086年左右)。足够学校用很久很久了。
-
作用1 (唯一性基础): 时间在一直往前走,不同毫秒生成的学生,学号的时间部分肯定不同。
-
作用2 (有序性): 后报到的学生,学号的时间部分一定比先报到的学生大。这样学号整体上就是按时间顺序增长的,方便排序和管理(就像按入学时间排学号)。
-
-
第3部分:工作机器ID (10 bits) - “班级编号”
-
学校有很多个报名点(不同的服务器/服务进程)。每个报名点需要有一个唯一的编号(比如001班,002班...)。
-
10位二进制最多能表示1024个不同的报名点(2^10=1024)。足够给学校里的每个报名点都分配一个唯一编号。
-
作用 (分布式隔离): 保证即使同一毫秒,在不同报名点报到的学生,他们的学号也不会冲突。因为班级编号不同。比如001班和002班在同一毫秒各报到一个学生,他们的时间部分相同,但班级编号不同。
-
-
第4部分:序列号 (12 bits) - “当天流水号”
-
想象同一个报名点(同一个班级),在同一毫秒内,可能有好几个学生同时来报到(高并发)。
-
12位序列号就是用来区分这些“同一毫秒、同一报名点”的学生的流水号(0001, 0002, 0003...)。
-
最多能区分4096个(2^12=4096)。如果某个报名点某一毫秒内涌进来超过4096个学生(极端高并发),怎么办?算法会“等一等”,等到下一毫秒再继续从0开始编号。
-
作用 (毫秒内唯一): 保证同一个报名点(服务器)在同一毫秒内产生的多个ID也是唯一的。
-
总结雪花算法的工作流程(发学号):
-
看时间: 获取当前的毫秒级时间戳。
-
看地点: 知道自己是哪个报名点(工作机器ID)。
-
看现场: 查询自己这个报名点、在当前这个毫秒内,已经发出了多少个学号(序列号)。
-
组学号: 把 [0] + [当前时间戳] + [我的报名点编号] + [下一个流水号] 这四部分拼装起来,形成一个完整的64位二进制数(最终展现为一个很大的长整数)。
-
记一笔: 更新一下记录,标记自己在这个毫秒内已经发到第几个流水号了。如果流水号用完了(达到4096),就等到下一个毫秒再发,流水号重置为0。
优势:
-
全局唯一: 时间戳(不同时间)+ 工作机器ID(不同地点)+ 序列号(同一时间地点区分)的组合,理论上保证了在整个分布式系统中生成的ID都是唯一的。
-
趋势递增(有序): 因为时间戳是不断增长的,生成的ID整体上是越来越大的。这对于数据库按主键索引插入数据非常高效(如MySQL InnoDB)。
-
高性能: 完全在本地生成(无需像数据库自增那样去中心节点申请),速度非常快。
-
可伸缩: 通过配置不同的工作机器ID,可以轻松支持大量的服务器节点。
时钟回拨
问题是什么?
想象学校用来计时的钟表,突然因为故障或同步问题往回走了(比如从10:00:05跳回到了10:00:03)。这会导致一个严重问题:雪花算法严重依赖时间递增。如果时间回拨了,算法就可能在新生成的ID中使用了一个过去的时间戳。而在这个“过去的时间戳”里,它之前可能已经发过一些学号了。这就可能导致生成重复的ID!
雪花算法怎么应对?
-
容忍小回拨: 算法可以设置一个最大容忍的时钟回拨时间(比如100毫秒)。如果发现时钟只回拨了一点点(小于这个阈值),它就暂停发号,等待系统时间“自然走”到超过它最后一次成功生成ID的时间点之后,再继续工作。这期间如果有生成ID的请求,可能会等待或报错(看具体实现)。
-
拒绝大回拨: 如果时钟回拨得太厉害(超过了设置的容忍阈值),算法会直接抛出错误,拒绝生成ID。因为这超出了它能安全处理的范围,必须由人工干预(检查并修复服务器时间同步问题)。
分布式序列配置
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把下面的内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称(自定义)——和下面的alg_hash_mod对应
keyGenerateStrategy: # 分布式序列策略
column: id # 列名
keyGeneratorName: alg_snowflake # 分布式序列算法名
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD # 分片算法类型
props:
sharding-count: 2 # 分片数量,表示根据分片列对2取模
# 分布式序列配置
keyGenerators:
alg_snowflake: # 分布式序列算法名
type: SNOWFLAKE # 分布式序列类型(雪花算法)
注意我们修改了配置文件的下面这些地方
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
写入测试
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
然后我们执行下面这个
清空之前的所有数据
TRUNCATE TABLE t_order;
接着我们执行INSERT语句,注意:这次我们不用指定主键的值
-- user_id=1的订单
insert into t_order (order_no, user_id, amount) values ('BIT001', 1, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT002', 1, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT003', 1, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT004', 1, 20.00);
-- user_id=2的订单
insert into t_order (order_no, user_id, amount) values ('BIT005', 2, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT006', 2, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT007', 2, 20.00);
insert into t_order (order_no, user_id, amount) values ('BIT008', 2, 20.00);
查看日志
# 数据根据分片策略写入到不同的数据节点,并生成了主键值
# BIT001的记录被路由到了server_order1 里的t_order0 表中,并生成了主键值
[INFO ] 2025-08-02 03:01:38.295 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT001', 1, 20.00)
[INFO ] 2025-08-02 03:01:38.295 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (order_no, user_id, amount, id) values ('BIT001', 1, 20.00, 1158237329177968640)
# BIT002的记录被路由到了server_order1 里的t_order1 表中,并生成了主键值
[INFO ] 2025-08-02 03:01:47.326 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT002', 1, 20.00)
[INFO ] 2025-08-02 03:01:47.327 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order1 (order_no, user_id, amount, id) values ('BIT002', 1, 20.00, 1158237367098671105)
# BIT003的记录被路由到了server_order1 里的t_order0 表中,并生成了主键值
[INFO ] 2025-08-02 03:01:59.026 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT003', 1, 20.00)
[INFO ] 2025-08-02 03:01:59.026 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order0 (order_no, user_id, amount, id) values ('BIT003', 1, 20.00, 1158237416167833600)
# BIT004的记录被路由到了server_order1 里的t_order1 表中,并生成了主键值
[INFO ] 2025-08-02 03:02:06.901 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT004', 1, 20.00)
[INFO ] 2025-08-02 03:02:06.901 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_order1 (order_no, user_id, amount, id) values ('BIT004', 1, 20.00, 1158237449197977601)
# BIT005的记录被路由到了server_order0 里的t_order0 表中,并生成了主键值
[INFO ] 2025-08-02 03:02:14.375 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT005', 2, 20.00)
[INFO ] 2025-08-02 03:02:14.375 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (order_no, user_id, amount, id) values ('BIT005', 2, 20.00, 1158237480546205696)
# BIT006的记录被路由到了server_order0 里的t_order1 表中,并生成了主键值
[INFO ] 2025-08-02 03:02:22.718 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT006', 2, 20.00)
[INFO ] 2025-08-02 03:02:22.718 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order1 (order_no, user_id, amount, id) values ('BIT006', 2, 20.00, 1158237515539283969)
# BIT007的记录被路由到了server_order0 里的t_order0 表中,并生成了主键值
[INFO ] 2025-08-02 03:02:29.942 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT007', 2, 20.00)
[INFO ] 2025-08-02 03:02:29.942 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order0 (order_no, user_id, amount, id) values ('BIT007', 2, 20.00, 1158237545838936064)
# BIT008的记录被路由到了server_order0 里的t_order1 表中,并生成了主键值
[INFO ] 2025-08-02 03:02:38.577 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: insert into t_order (order_no, user_id, amount) values ('BIT008', 2, 20.00)
[INFO ] 2025-08-02 03:02:38.577 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_order1 (order_no, user_id, amount, id) values ('BIT008', 2, 20.00, 1158237582056751105)
2.9 多表关联查询
还记得我们的业务吗?
一个用户可能会有多个订单,而且每个订单也有多个订单信息。
在真实的电商业务场景中,订单数据通常与订单详情数据紧密关联。其中:
-
订单表 (
t_order
) 记录订单的核心信息(如订单号、用户ID、总金额、状态等)。 -
订单详情表 (
t_order_item
) 则记录用户购买的具体商品信息,包括商品ID、单价、购买数量等。一个订单可能包含多个商品项,因此订单 (t_order
) 与订单详情 (t_order_item
) 之间是典型的一对多关系。
为了优化数据库性能并避免复杂的跨库关联查询(JOIN),一个关键的设计原则是:将同一个用户产生的订单数据及其关联的所有订单详情数据,存储在同一个物理数据源(分片)中。
基于此原则:
-
t_order
表的分片策略: 通常会选择user_id
作为分片键(Sharding Key)。这样能确保同一用户的所有订单都落在同一个分片上。 -
t_order_item
表的分片策略: 为了实现上述“同用户订单及详情同库”的目标,t_order_item
表必须采用与t_order
表相同的分片策略。虽然t_order_item
表本身与订单直接关联的字段是order_no
(订单号),但仅靠order_no
无法保证其与父订单t_order
记录在同一分片。
因此,在 t_order_item
表中:
-
需要冗余添加
user_id
字段。这个字段来源于其所属订单 (t_order
) 中的user_id
。 -
同时,
t_order_item
表的分片键也需要包含user_id
(通常会与order_no
组合使用,但核心是user_id
确保分片路由)。
关键补充说明:
-
为什么需要
user_id
? 订单详情 (t_order_item
) 通过order_no
关联到订单 (t_order
)。如果t_order_item
只按order_no
分片,而t_order
按user_id
分片,那么一个用户的不同订单可能分布在不同的分片上(如果user_id
相同但order_no
的哈希值不同)。这会导致查询某个用户的所有订单详情时,需要跨多个分片扫描,性能低下。 -
冗余
user_id
的作用: 在t_order_item
表中添加user_id
字段(即使逻辑上可通过order_no
关联到t_order.user_id
),是为了让t_order_item
表直接拥有user_id
这个信息。这样,t_order_item
就可以直接使用user_id
作为分片键(或作为组合分片键的主要部分)。 -
分片策略一致性: 当
t_order
和t_order_item
都使用user_id
(或user_id
+order_no
等组合) 作为分片键时,系统就能保证:-
同一个
user_id
下的所有t_order
记录落在分片 A。 -
同一个
user_id
下的所有t_order_item
记录也必然落在同一个分片 A。
-
-
避免跨库关联: 由于同一个用户的所有订单及其关联的所有订单详情都存储在同一个物理分片上,任何需要查询“用户+订单+订单详情”的操作(例如:查询用户张三的所有订单及其包含的商品),都只需要访问一个分片即可完成,无需跨分片进行低效的 JOIN 操作,极大提升了查询性能。
2.9.1 配置策略
创建关联表
分别在server-order0和server-order1服务器创建订单详情表t_order_item0和t_order_item1,表结构如下:
-- 创建订单详情 t_order_item0
CREATE TABLE IF NOT EXISTS t_order_item0 (
id BIGINT PRIMARY KEY COMMENT '主键ID',
order_no VARCHAR(30) COMMENT '订单号',
user_id BIGINT COMMENT '用户编号',
price DECIMAL(12, 2) COMMENT '商品单价',
count INT COMMENT '商品个数'
);
-- 创建订单详情 t_order_item1
CREATE TABLE IF NOT EXISTS t_order_item1 (
id BIGINT PRIMARY KEY COMMENT '主键ID',
order_no VARCHAR(30) COMMENT '订单号',
user_id BIGINT COMMENT '用户编号',
price DECIMAL(12, 2) COMMENT '商品单价',
count INT COMMENT '商品个数'
);
我们登陆server-order0去看看
mysql -h127.0.0.1 -P63310 -u root -p
接着我们登陆server-order1去看看
mysql -h127.0.0.1 -P63311 -u root -p
配置策略
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把下面的内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称(自定义)——和下面的alg_hash_mod对应
keyGenerateStrategy: # 分布式序列策略
column: id # 列名
keyGeneratorName: alg_snowflake # 分布式序列算法名
t_order_item: # 逻辑表名t_order_item
actualDataNodes: server_order${0..1}.t_order_item${0..1} # 真实数据节点
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
#shardingAlgorithmName: alg_db_inline_userid # 分片算法名称
shardingAlgorithmName: alg_mod # 分片算法名称
tableStrategy: # 分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称
keyGenerateStrategy: # 分布式序列策略
column: id # 列名
keyGeneratorName: alg_snowflake # 分布式序列算法名
# 分片算法配置
shardingAlgorithms:
alg_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD # 分片算法类型
props:
sharding-count: 2 # 分片数量,表示根据分片列对2取模
# 分布式序列配置
keyGenerators:
alg_snowflake: # 分布式序列算法名
type: SNOWFLAKE # 分布式序列类型(雪花算法)
注意:其实我就添加了下面这些
现在我们就有3个逻辑表了
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
2.9.2.测试
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
构造数据
为每个订单构造订单详情记录,SQL语句如下:
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT001', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT001', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT001', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT002', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT002', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT002', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT003', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT003', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT003', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT004', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT004', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT004', 1, 10, 3);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT005', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT005', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT005', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT006', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT006', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT006', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT007', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT007', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT007', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT008', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT008', 2, 20, 5);
INSERT INTO t_order_item (order_no, user_id, price, count) VALUES ('BIT008', 2, 20, 5);
在各个数据节点验证是否写入成功
关联查询
关联查询时,使用分片键进行表关联,为了后面使用绑定表,ShardinSphere可以根据分片策略帮
我们自动路由到对应的数据节点
也可以使用订单号进行关联,但是ShardinSphere无法感知到目标数据在哪个数据节点,会在所有
数据源中进行查询,对效率的影响比较大
根据条件查询订单和订单详情信息
-- 查询所有订单和详情信息
select * from t_order o, t_order_item i where o.order_no = i.order_no;
查看日志
日志内容如下
逻辑SQL
[INFO ] 2025-08-02 07:50:44.323 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: select * from t_order o, t_order_item i where o.order_no = i.order_no
实际执行SQL
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order0 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order1 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order0 o, t_order_item1 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order1 o, t_order_item1 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order0 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order1 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order0 o, t_order_item1 i where o.order_no = i.order_no
[INFO ] 2025-08-02 07:50:44.324 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order1 o, t_order_item1 i where o.order_no = i.order_no
上面红色字体表示不应该存在的!!!
问题现象:
在执行逻辑 SQL SELECT * FROM t_order o, t_order_item i WHERE o.order_no = i.order_no 时,ShardingSphere 将其路由为 8 条实际 SQL(如日志所示),分别在两个数据源 (server_order0, server_order1) 上对所有物理表 (t_order0, t_order1, t_order_item0, t_order_item1) 的组合进行了笛卡尔积式的关联查询。
核心问题:无效查询与资源浪费
这种全组合路由方式导致了严重的资源浪费。
根据已配置的分片策略规则:
- t_order0 表中的订单,其关联的订单详情必定存储在 t_order_item0 表中。
- t_order1 表中的订单,其关联的订单详情必定存储在 t_order_item1 表中。
这意味着:
-
有效关联组合只有两种:
-
t_order0
JOINt_order_item0
-
t_order1
JOINt_order_item1
-
-
无效关联组合同样有两种:
-
t_order0
JOINt_order_item1
(跨表后缀组合) -
t_order1
JOINt_order_item0
(跨表后缀组合)
-
无效查询分析:
在生成的8条实际SQL中:
每个数据源包含4条SQL(2种有效组合 + 2种无效组合)
两个数据源共产生 4条无效SQL(每个数据源各2条)
具体无效SQL示例:
- server_order0 ::: select * from t_order0 o, t_order_item1 i where o.order_no = i.order_no
- server_order0 ::: select * from t_order1 o, t_order_item0 i where o.order_no = i.order_no
- server_order1 ::: select * from t_order0 o, t_order_item1 i where o.order_no = i.order_no
- server_order1 ::: select * from t_order1 o, t_order_item0 i where o.order_no = i.order_no
后果:
这4条无效SQL:
- 必然返回空结果集:因分片规则保证数据不会存在于跨后缀表中
- 消耗50%的查询资源:在总共8条查询中占一半比例
- 增加数据库负载:需要执行全表扫描或索引查找
- 延长响应时间:尤其在大数据量场景下性能影响显著
优化方向:
- 通过配置绑定表(Binding Table)关系,告知ShardingSphere:
- t_order 和 t_order_item 具有相同的分片规则
相同分片键值的数据必定落在相同后缀的物理表中
配置后,ShardingSphere将只路由有效组合:
- server_order0 ::: t_order0 JOIN t_order_item0
- server_order0 ::: t_order1 JOIN t_order_item1
- server_order1 ::: t_order0 JOIN t_order_item0
- server_order1 ::: t_order1 JOIN t_order_item1
从而将查询数量从8条优化至4条,完全消除4条无效查询,提升50%的执行效率。
2.9.3. 绑定表
什么是绑定表?
我们用学校储物柜的例子,把「绑定表」讲得像故事一样清楚:
📦 想象一个超大校园(分布式数据库)
-
学生(订单表
t_order
) 太多,学校建了 两栋宿舍楼(分片库server_order0
,server_order1
) 来住。 -
每栋楼里,又按 班级(分片表) 分了储物柜:
-
t_order0
表 → 代表 1班 的订单储物柜(放在两栋楼里) -
t_order1
表 → 代表 2班 的订单储物柜(放在两栋楼里)
-
-
书本(订单详情
t_order_item
) 也要存,同样分柜子:-
t_order_item0
→ 1班 的书本柜 -
t_order_item1
→ 2班 的书本柜
-
关键规则📌:
1班学生的书本,只会放在1班书本柜;
2班学生的书本,只会放在2班书本柜。
(这就是分片策略:按班级(如user_id
)分片,保证同班数据在一起)
❌ 问题场景:查“张三的订单和书本”
-
没绑定表时:管理员懵了!
-
他跑到 宿舍楼A (
server_order0
),翻遍所有组合:-
1班订单柜 + 1班书本柜 ✅ (可能有张三)
-
1班订单柜 + 2班书本柜 ❌ (白翻!1班书本不可能在2班柜)
-
2班订单柜 + 1班书本柜 ❌ (白翻!2班书本不可能在1班柜)
-
2班订单柜 + 2班书本柜 ✅ (可能有张三)
-
-
接着又跑去 宿舍楼B (
server_order1
),同样翻4遍柜子组合。 -
结果:总共翻了 8次柜子,其中 4次是白费力气(翻错班级组合)!
-
✅ 解决方案:告诉系统「班级柜子是一套的!」(绑定表)
绑定后的智能搜索🔍:
管理员现在懂了规则:
-
只查配套柜子!
-
找 1班 的张三 → 只看 1班订单柜 + 1班书本柜(绝不去翻2班书本柜)
-
找 2班 的张三 → 只看 2班订单柜 + 2班书本柜(绝不去翻1班书本柜)
-
-
每栋楼只查2次有效组合:
-
宿舍楼A:查
(1班订单柜 + 1班书本柜)
+(2班订单柜 + 2班书本柜)
-
宿舍楼B:同样查这两组有效配套
-
-
结果:总共只翻 4次柜子,0次无效劳动!效率翻倍!
🌟 绑定表的核心作用
像配对钥匙和锁🔑:告诉数据库系统哪些表是“一套”的(分片规则完全一致),查询时自动跳过无效组合,直奔正确分片!
💡 你的日志发生了什么?
场景 | 执行的SQL数 | 有效查询 | 无效查询 |
---|---|---|---|
未绑定表 | 8条 | 4条 (同班级配套查询) | 4条 (跨班级错误组合) |
绑定表后 | 4条 | 4条 (同班级配套) | 0条 (系统跳过无效组合) |
效果:资源消耗减半,查询速度更快,数据库再也不做“无用功”!
我们现在就来配置绑定表!!
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把下面的内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称(自定义)——和下面的alg_hash_mod对应
keyGenerateStrategy: # 分布式序列策略
column: id # 列名
keyGeneratorName: alg_snowflake # 分布式序列算法名
t_order_item: # 逻辑表名t_order_item
actualDataNodes: server_order${0..1}.t_order_item${0..1} # 真实数据节点
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
#shardingAlgorithmName: alg_db_inline_userid # 分片算法名称
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_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD # 分片算法类型
props:
sharding-count: 2 # 分片数量,表示根据分片列对2取模
# 分布式序列配置
keyGenerators:
alg_snowflake: # 分布式序列算法名
type: SNOWFLAKE # 分布式序列类型(雪花算法)
注意我只是修改了下面这一处
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
测试
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
测试
- 执行查询语句
-- 查询所有订单和详情信息
select * from t_order o, t_order_item i where o.order_no = i.order_no;
查看日志,实际SQL使用了正确的数据节点
[INFO ] 2025-08-02 08:07:35.521 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Logic SQL: select * from t_order o, t_order_item i where o.order_no = i.order_no
[INFO ] 2025-08-02 08:07:35.521 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order0 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 08:07:35.521 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_order1 o, t_order_item1 i where o.order_no = i.order_no
[INFO ] 2025-08-02 08:07:35.521 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order0 o, t_order_item0 i where o.order_no = i.order_no
[INFO ] 2025-08-02 08:07:35.521 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_order1 o, t_order_item1 i where o.order_no = i.order_no
现在我们就实现了
- t_order0 表中的订单,其关联的订单详情必定存储在 t_order_item0 表中。
- t_order1 表中的订单,其关联的订单详情必定存储在 t_order_item1 表中。
2.11 广播表
2.11.1.什么是广播表
在数据库中有些表中存的数据一般不怎么改变,比如用于配置的表,也就是常说的字典表,这种类型的表在表关联查询时也会被经常使用,在分布式场景中,这类表在每个数据源中都保存一个复本,可以减少跨库关联提升查询效率。
广播表: 指所有分片数据源中都存在的表,表结构及表中的数据在每个数据库中完全一致,适用于数据量不大且需要与海量数据表进行关联的场景。
广播表具有以下特性:
- 插入、更新操作会实时在所有数据节点上执行,保持各个分片的数据一致性
- 查询时只从一个数据节点获取数据
- 可以和任何一个表进行表关联查询
深入理解广播表
我们用「连锁便利店」的例子,把广播表讲得明明白白:
🏪 想象一个全国连锁便利店(分布式数据库)
-
你有上千家分店(分片数据库),每家店都有:
-
本地销售记录表(分片表):记录本店的订单(比如
北京店_订单表
、上海店_订单表
) -
本地商品库存表(分片表):记录本店的库存(比如
北京店_库存表
、上海店_库存表
)
-
-
但总有些东西是所有店统一的:
-
📖 《商品总目录手册》(这就是广播表!)
-
包含所有商品信息:商品ID、名称、分类、建议售价...
-
关键:每家店都有一本完全相同的《手册》!
-
-
📣 为什么叫“广播”表?
就像总部发广播通知:
“所有分店注意!最新版《商品手册》已发布,请立刻放在收银台!”
于是所有分店同时更新手册,保持全国统一。
✅ 广播表的三大特性(用便利店解释)
1️⃣ 数据全局一致:同款手册,每家店都有
-
场景:上架新商品“螺蛳粉月饼”(商品ID=100)
-
操作:总部更新《手册》,自动同步到所有分店
-
结果:北京店、上海店... 所有店的《手册》第100页都是“螺蛳粉月饼”
📌 这就是:插入/更新实时同步所有节点
2️⃣ 查询高效:随便找家店问就行
-
场景:程序员想知道“商品ID=100是什么?”
-
操作:系统随机选一家店(比如北京店),翻开它的《手册》
-
结果:直接得到答案:“螺蛳粉月饼”,无需联系其他店
📌 这就是:查询只需访问一个节点
3️⃣ 关联查询神器:和本地表无缝搭配
-
场景:查“北京店今天卖了哪些商品?显示商品名和售价”
-
表关联:
北京店_销售记录表
(只有商品ID)JOIN《商品手册》
(有ID和名称) -
操作:
北京店员工自己就能搞定!-
翻本店销售记录 → “卖了商品ID=100,5盒”
-
翻本店的《手册》 → “ID=100是螺蛳粉月饼,售价¥99”
-
合并结果 → “螺蛳粉月饼,售价¥99,5盒”
-
-
优势:不用打电话问总部,也不用查其他分店!
📌 这就是:可与任意表关联,避免跨库查询
❌ 如果没有广播表会怎样?
-
每次查商品名都要打电话问总部(跨网络访问中心库)
-
总部电话被打爆(中心库压力大)
-
响应超慢(网络延迟)
-
北京店无法独立完成“销售记录+商品名”的查询
🌟 哪些数据适合做广播表?(便利店例子)
-
商品字典表(所有店共用商品信息)→ 📖 手册
-
城市编码表(北京=010,上海=021)→ 🏙️ 行政区划手册
-
门店信息表(店长、地址、电话)→ 📞 分店通讯录
-
支付方式表(现金、支付宝、微信)→ 💳 支付标识卡
✅ 共同点:数据量小、改动少、所有业务都用到
2.11.2 配置广播表
在所有数据源创建广播表
在所有数据源 server-user,server-order0,server-order1 中创建广播表
-- 创建⼴播表t_dict,不使⽤⾃增主键,由应⽤程序传⼊
create table if not exists t_dict (
id bigint primary key,
type varchar(30) comment '类型'
);
在server-user里面创建广播表
在server-order0里面创建广播表
在server-order1里面创建广播表
修改配置文件,添加广播表规则
# 规则配置
rules:
# 分片配置
!SHARDING
tables:
# 逻辑表配置
# ... 省略
# 绑定表规则
bindingTables:
# ... 省略
# 分片算法配置
shardingAlgorithms:
# ... 省略
# 分布式序列配置
keyGenerators:
# ... 省略
注意这次我们 需要配置的文件是: conf/config-sharding.yaml,对应我们宿主机的目录就是/bit/shardingsphere/proxy/conf/config-sharding.yaml。
首先我们先来到我们的/bit/shardingsphere/proxy/conf/,然后创建一个叫config-sharding.yaml的文件
cd /bit/shardingsphere/proxy/conf/
vim config-sharding.yaml
我们把下面的内容粘贴进去
databaseName: bit_sharding_db
dataSources:
server_user:
url: jdbc:mysql://150.158.139.86:53310/user_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order0:
url: jdbc:mysql://150.158.139.86:63310/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
server_order1:
url: jdbc:mysql://150.158.139.86:63311/order_db?characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
rules:
- !SHARDING
tables:
t_user: # 逻辑表名
actualDataNodes: server_user.t_user # 由数据源名 + 表名组成
t_order: # 逻辑表名
actualDataNodes: server_order${0..1}.t_order${0..1}
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
shardingAlgorithmName: alg_mod # 分片算法名称(自定义)——和下面的alg_mod对应
tableStrategy: #分表策略
standard:
shardingColumn: order_no # 分片列名称
shardingAlgorithmName: alg_hash_mod # 分片算法名称(自定义)——和下面的alg_hash_mod对应
keyGenerateStrategy: # 分布式序列策略
column: id # 列名
keyGeneratorName: alg_snowflake # 分布式序列算法名
t_order_item: # 逻辑表名t_order_item
actualDataNodes: server_order${0..1}.t_order_item${0..1} # 真实数据节点
databaseStrategy: # 分库策略
standard: # 用于单分片键的标准分片场景
shardingColumn: user_id # 分片列名称
#shardingAlgorithmName: alg_db_inline_userid # 分片算法名称
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_db_inline_userid: # 自定义的分片算法名称_行表达式分片算法
type: INLINE # 分片算法类型
props:
algorithm-expression: server_order${user_id % 2} # 分片算法,根据 user_id对2取模
alg_mod: # 自定义算法名_取模分片算法
type: MOD
props:
sharding-count: 2
alg_hash_mod:
type: HASH_MOD # 分片算法类型
props:
sharding-count: 2 # 分片数量,表示根据分片列对2取模
# 分布式序列配置
keyGenerators:
alg_snowflake: # 分布式序列算法名
type: SNOWFLAKE # 分布式序列类型(雪花算法)
# 广播表
broadcastTables:
- t_dict
注意我只是修改了下面这一处
我们保存退出。重启Docker容器
docker restart ss-proxy
docker ps
运行结果
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker restart ss-proxy
ss-proxy
root@VM-16-14-ubuntu:/bit/shardingsphere/proxy/conf# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d9e2e351fb8a mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63311->3306/tcp, [::]:63311->3306/tcp server-order1
d2d5c33d7ca2 mysql:8.0.42 "docker-entrypoint.s…" 22 hours ago Up 22 hours 33060/tcp, 0.0.0.0:63310->3306/tcp, [::]:63310->3306/tcp server-order0
b5051d4517c4 mysql:8.0.42 "docker-entrypoint.s…" 29 hours ago Up 8 hours 33060/tcp, 0.0.0.0:53310->3306/tcp, [::]:53310->3306/tcp server-user
efc3b3c7dabc apache/shardingsphere-proxy:5.3.2 "/bin/sh -c '${LOCAL…" 47 hours ago Up 33 seconds 0.0.0.0:3307->3307/tcp, [::]:3307->3307/tcp ss-proxy
测试
我们先去打开实时日志
tail -f /bit/shardingsphere/proxy/logs/shardingsphere.log
然后我们去登陆进这个服务器来
mysql -h127.0.0.1 -P3307 -uroot -p
测试
插入一条记录,主键由应用程序传入
-- 向广播表中写入记录
insert into t_dict (id, type) values (1, 'ADMIN');
查看日志,实际SQL使用了正确的数据节点
逻辑SQL
[INFO ] 2025-08-02 09:55:37.848 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Logic SQL: insert into t_dict (id, type) values (1, 'ADMIN')
实际执行SQL
[INFO ] 2025-08-02 09:55:37.848 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_user ::: insert into t_dict (id, type) values (1, 'ADMIN')
[INFO ] 2025-08-02 09:55:37.848 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: insert into t_dict (id, type) values (1, 'ADMIN')
[INFO ] 2025-08-02 09:55:37.848 [ShardingSphere-Command-0] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: insert into t_dict (id, type) values (1, 'ADMIN')
由此可见ShardinSphere向所有数据源中都写入了记录。
查询测试
select * from t_dict;
我们多次执行上面那个SQL语句,然后我们查看日志
发现就会出现下面这种情况
从server_order0节点获取数据
[INFO ] 2025-08-02 09:57:33.481 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: select * from t_dict
[INFO ] 2025-08-02 09:57:33.482 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: select * from t_dict
从server_order1节点获取数据
[INFO ] 2025-08-02 09:57:34.272 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: select * from t_dict
[INFO ] 2025-08-02 09:57:34.272 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: select * from t_dict
从server_user节点获取数据
[INFO ] 2025-08-02 09:58:32.256 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Logic SQL: select * from t_dict
[INFO ] 2025-08-02 09:58:32.256 [ShardingSphere-Command-1] INFO ShardingSphere-SQL - Actual SQL: server_user ::: select * from t_dict
可以看到啊,获取数据的时候是随机从几个数据节点获取数据的
至此,水平分片就算是讲完了,内容很多,请大家耐心阅读。