大家好,我是工藤学编程 🦉 | 一个正在努力学习的小博主,期待你的关注 |
---|---|
实战代码系列最新文章😉 | C++实现图书管理系统(Qt C++ GUI界面版) |
SpringBoot实战系列🐷 | 【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战 |
环境搭建大集合 | 环境搭建大集合(持续更新) |
分库分表 | 分库分表下的 ID 冲突问题与雪花算法讲解 |
前情摘要:
1、数据库性能优化
2、分库分表之优缺点分析
3、分库分表之数据库分片分类
4、分库分表之策略
5、分库分表技术栈讲解-Sharding-JDBC
6、分库分表下的 ID 冲突问题与雪花算法讲解
本文章目录
分库分表之实战-sharding-JDBC
一、SpringBoot2.5+MybatisPlus+Sharding-Jdbc项目创建
1. 在创建好的Maven项目的pom文件中添加以下pom文件依赖:
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot.version>2.5.5</spring.boot.version>
<mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version>
<lombok.version>1.18.16</lombok.version>
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<junit.version>4.12</junit.version>
<druid.version>1.1.16</druid.version>
<!--跳过单元测试-->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!--mybatis plus和springboot整合-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.boot.starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
2. 创建对应测试的数据库表
- 新建数据库ccc_shop_order_0
CREATE DATABASE `ccc_shop_order_0`
CHARACTER SET utf8mb4
COLLATE utf8mb4_bin;
- 新建数据库ccc_shop_order_1
CREATE DATABASE `ccc_shop_order_1`
CHARACTER SET utf8mb4
COLLATE utf8mb4_bin;
- 在数据库ccc_shop_order_0中新建表product_order_0、product_order_1
use ccc_shop_order_0;
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订
单唯⼀标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未⽀
付订单,PAY已经⽀付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单⽣
成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订
单实际⽀付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '⽤户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_bin;
CREATE TABLE `product_order_1` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订
单唯⼀标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未⽀
付订单,PAY已经⽀付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单⽣
成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订
单实际⽀付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '⽤户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_bin;
- 在数据库ccc_shop_order_1中新建表product_order_0、product_order_1
use ccc_shop_order_1;
CREATE TABLE `product_order_0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订
单唯⼀标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未⽀
付订单,PAY已经⽀付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单⽣
成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订
单实际⽀付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '⽤户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_bin;
CREATE TABLE `product_order_1` (
`id` bigint NOT NULL AUTO_INCREMENT,
`out_trade_no` varchar(64) DEFAULT NULL COMMENT '订
单唯⼀标识',
`state` varchar(11) DEFAULT NULL COMMENT 'NEW 未⽀
付订单,PAY已经⽀付订单,CANCEL超时取消订单',
`create_time` datetime DEFAULT NULL COMMENT '订单⽣
成时间',
`pay_amount` decimal(16,2) DEFAULT NULL COMMENT '订
单实际⽀付价格',
`nickname` varchar(64) DEFAULT NULL COMMENT '昵称',
`user_id` bigint DEFAULT NULL COMMENT '⽤户id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_bin;
3、创建实体类以及对应数据库实体类
ProductOrderDO
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("product_order")
public class ProductOrderDO {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String outTradeNo;
private String state;
private Date createTime;
private Double payAmount;
private String nickname;
private Long userId;
}
ProductOrderMapper
public interface ProductOrderMapper extends
BaseMapper<ProductOrderDO> {
}
目前我的工程如下,大家可以对比一下
二、 Sharding-Jdbc常规数据源配置和水平分表
配置appliaction.properties
spring.application.name=ccc-sharding-jdbc
server.port=8080
# 打印执⾏的数据库以及语句
spring.shardingsphere.props.sql.show=true
# 数据源 db0,如果有多个用逗号分隔,有多少个,下面具体数据库就要配置多少个
spring.shardingsphere.datasource.names=ds0,ds1
# 第⼀个数据库
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/ccc_shop_order_0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# 第二个数据库
spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://127.0.0.1:3306/ccc_shop_order_1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456
# 指定product_order表的数据分布情况,配置数据节点,⾏表达式标识符使⽤ ${...} 或 $->{...},但前者与 Spring 本身的⽂件占位符冲突,所以在 Spring 环境中建议使⽤ $->{...}
spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1}
# 指定product_order表的分⽚策略,分⽚策略包括【分⽚键和分⽚算法】
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
这里我们使用的是mysql8.0
- spring.shardingsphere.datasource.ds0.password=123456 这里大家记得修改为自己数据库的密码
- spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://127.0.0.1:3306/ccc_shop_order_0?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true 这里大家记得把ip改为自己的安排(如果数据库不在本地)
ShardingSphere SQL 执行日志解析
编写单元测试DbTest
@RunWith(SpringRunner.class) //底层⽤junitSpringJUnit4ClassRunner
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class DbTest {
@Autowired
private ProductOrderMapper productOrderMapper;
@Test
public void testSaveProductOrder(){
for(int i=0;i<10;i++){
ProductOrderDO productOrder = new ProductOrderDO();
productOrder.setCreateTime(new Date());
productOrder.setNickname("ccc_i="+i);
productOrder.setOutTradeNo(UUID.randomUUID().toString().substring(0,32));
productOrder.setPayAmount(100.00);
productOrder.setState("PAY");
productOrder.setUserId(Long.valueOf(i+""));
productOrderMapper.insert(productOrder);
}
}
}
执行结果如下
我们查看控制台输出,展示了从逻辑 SQL 到实际 SQL 的路由过程。我来逐行解释:
1. Logic SQL(逻辑 SQL)
INSERT INTO product_order (out_trade_no, state, create_time, pay_amount, nickname, user_id)
VALUES (?, ?, ?, ?, ?, ?)
这是应用程序实际执行的 SQL 语句:
- 操作对象:
product_order
表(逻辑表名) - 参数占位符:
?
代表实际参数会在执行时传入
2. Actual SQL(实际 SQL)
ds0 ::: INSERT INTO product_order_0 (out_trade_no, state, create_time, pay_amount, nickname, user_id)
VALUES (?, ?, ?, ?, ?, ?)
这是 ShardingSphere 根据分片规则路由后实际执行的 SQL:
ds0
:目标数据源(数据库实例)product_order_0
:物理表名- 可以看到,逻辑表
product_order
被路由到了物理表product_order_0
3. 绑定参数(实际参数值)
::: [d861e3e2-720a-4d16-bb67-12f96d31, PAY, 2025-06-30 16:24:23.075, 100.0, ccc_i=0, 0]
这些是占位符 ?
对应的实际参数值:
out_trade_no
:d861e3e2-720a-4d16-bb67-12f96d31
(交易号)state
:PAY
(支付状态)create_time
:2025-06-30 16:24:23.075
(创建时间)pay_amount
:100.0
(支付金额)nickname
:ccc_i=0
(用户昵称)user_id
:0
(用户ID,分片键)
4. 分片路由逻辑分析
根据我们之前的配置:
spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
当 user_id=0
时:
0 % 2 = 0
→ 路由到product_order_0
表- 所以这条记录被写入
ds0
库的product_order_0
表
三、分库分表下的 ID 冲突问题解决
在 分库分表下的 ID 冲突问题与雪花算法讲解中,我们提到了,同时我们前面的结果表明使用的时候,也确实存在ID冲突问题。因此我们需要使用雪花算法解决这个问题。
- 方式⼀
订单id使用MybatisPlus的配置,ProductOrder类配置@TableId(value = “id”, type = IdType.ASSIGN_ID)默认实现类为DefaultIdentifierGenerator雪花算法
- 方式⼆
使⽤Sharding-Jdbc配置⽂件,注释DO类里面的id分配策略
spring.shardingsphere.sharding.tables.product_o
rder.key-generator.column=id
spring.shardingsphere.sharding.tables.product_o
rder.key-generator.type=SNOWFLAKE
这里我使用的是方式2,让我们再重新运行测试函数,查看结果
觉得有用请点赞收藏!
如果有相关问题,欢迎评论区留言讨论~