首页技术栈归档照片墙音乐日记随想收藏夹友链留言关于

Sharding-JDBC

写作时间:2026-07-08

ShardingJdbc的基本介绍

概述

官网:http://shardingsphere.apache.org/index_zh.html 下载地址:https://shardingsphere.apache.org/document/current/cn/downloads/ 快速入门:https://shardingsphere.apache.org/document/current/cn/quick-start/shardingsphere-jdbc-quick-start/

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

Apache ShardingSphere 旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。

Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。

ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

认识shardingjdbc

定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。

支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库认识

shardingjdbc功能架构图

认识Sharding-Proxy

定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL 版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。

  • 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。
  • 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。

三个组件的比较

Sharding-Jdbc Sharding-Proxy Sharding-Sidecar
数据库 任意 MYSQL MYSQL
连接消耗数 高 低 低
异构语言 仅Java 任意 任意
性能 损耗低 损耗高 损耗低
中心化 是 否 是
静态入口 无 有 无

ShardingJdbc混合架构

ShardingSphere-JDBC 采用无中心化架构,适用于 Java 开发的高性能的轻量级 OLTP(连接事务处理) 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,适用于 OLAP(连接数据分析) 应用以及对分片数据库进行管理和运维的场景。

Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合与当前业务的最佳系统架构。

ShardingShpere的功能清单

  • 功能列表
    • 数据分片
    • 分库 & 分表
    • 读写分离
    • 分片策略定制化
    • 无中心化分布式主键
  • 分布式事务
    • 标准化事务接口
    • XA 强一致事务
    • 柔性事务
    • 数据库治理
  • 分布式治理
    • 弹性伸缩
    • 可视化链路追踪
    • 数据加密

ShardingSphere数据分片内核剖析

ShardingSphere 的 3 个产品的数据分片主要流程是完全一致的。 核心由 SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并的流程组成。

SQL 解析

分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。

执行器优化

合并和优化分片条件,如 OR 等。

SQL 路由

根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。

SQL 改写

将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。

SQL 执行

通过多线程执行器异步执行。

结果归并

将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。

ShardingJdbc环境搭建

Linux安装MySQL5.7

可以直接使用宝塔安装或者一下方式

下载mysql的rpm地址

http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/

配置Mysql扩展源,不需要下载rpm执行下面的安装

rpm -ivh http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql57-community-release-el7-10.noarch.rpm

yum安装mysql

yum install mysql-community-server -y

启动Mysql,并加入开机自启

systemctl start mysqldsystemctl stop mysqldsystemctl enable mysqld

使用Mysq初始密码登录数据库

systemctl start mysqld  #启动mysql
systemctl status mysqld  #查看mysql状态
systemctl stop mysqld  #关闭mysql
systemctl enable mysqld

使用Mysq初始密码登录数据库

#可以直接回车,默认没有密码,如果有密码在执行

grep "password" /var/log/mysqld.log
 mysql -uroot -pma1S8xjuEA/F
或者一步到位的做法如下
mysql -uroot -p$(awk '/temporary password/{print $NF}' /var/log/mysqld.log)

修改数据库密码

数据库默认密码规则必须携带大小写字母、特殊符号,字符长度大于8否则会报错。 因此设定较为简单的密码时需要首先修改set global validate_password_policy和_length参数值。

mysql> set global validate_password_policy=0;
Query OK, 0 rows affected (0.00 sec)
mysql> set global validate_password_length=1;
Query OK, 0 rows affected (0.00 sec)

修改密码

mysql> set password for root@localhost = password('mkxiaoer');
Query OK, 0 rows affected, 1 warning (0.00 sec)
或者
mysql>ALTER USER 'root'@'localhost' IDENTIFIED BY 'new password';

登录测试

[root@http-server ~]# mysql -uroot -pmkxiaoer
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)
mysql> exit

可视化工具的登录授权:(如果授权不成功,请查看防火墙)

操作完成上面的,现在还不能用可视化的客户端进行连接,需要我们进行授权:

mysql > grant all on *.* to root@'%' identified by '数据库密码';
mysql > flush privileges;

操作完毕,接下来可以使用navicat或者sqlylog进行远程连接了.

MySql完成主从复制

概述

主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。

复制是异步的 从站不需要永久连接以接收来自主站的更新。

根据配置,您可以复制数据库中的所有数据库,所选数据库甚至选定的表。

MySQL中复制的优点包括:

  • 横向扩展解决方案 - 在多个从站之间分配负载以提高性能。在此环境中,所有写入和更新都必须在主服务器上进行。但是,读取可以在一个或多个从设备上进行。该模型可以提高写入性能(因为主设备专用于更新),同时显着提高了越来越多的从设备的读取速度。
  • 数据安全性 - 因为数据被复制到从站,并且从站可以暂停复制过程,所以可以在从站上运行备份服务而不会破坏相应的主数据。
  • 分析 - 可以在主服务器上创建实时数据,而信息分析可以在从服务器上进行,而不会影响主服务器的性能。
  • 远程数据分发 - 您可以使用复制为远程站点创建数据的本地副本,而无需永久访问主服务器。

Replication 的原理

![](/upload/articles/Replication 的原理.png)

前提是作为主服务器角色的数据库服务器必须开启二进制日志

主服务器上面的任何修改都会通过自己的 I/O tread(I/O 线程)保存在二进制日志 Binary log 里面。

  • 从服务器上面也启动一个 I/O thread,通过配置好的用户名和密码, 连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log(中继日志)里面。
  • 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。 每个从服务器都会收到主服务器二进制日志的全部内容的副本。
  • 从服务器设备负责决定应该执行二进制日志中的哪些语句。 除非另行指定,否则主从二进制日志中的所有事件都在从站上执行。 如果需要,您可以将从服务器配置为仅处理一些特定数据库或表的事件。

前提准备:两台mysql

一台主机(master),一台从机(slave)

配置如下

Master节点配置/etc/my.cnf (master节点执行)

vim /etc/my.cnf

## 同一局域网内注意要唯一
server-id=100  
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed

Slave节点配置/etc/my.cnf (slave节点执行)

vim /etc/my.cnf

## 设置server_id,注意要唯一
server-id=101
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
## relay_log配置中继日志
relay_log=edu-mysql-relay-bin
##复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 如果需要同步函数或者存储过程
log_bin_trust_function_creators=true
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062

重启两台mysql

systemctl start mysqld

在master服务器授权slave服务器可以同步权限(master节点执行)

mysql > mysql -uroot -pmaster的密码
# 授予slave服务器可以同步master服务
mysql > grant replication slave, replication client on *.* to 'root'@'slave服务的ip' identified by 'slave服务器的密码';
mysql > flush privileges;
# 查看MySQL现在有哪些用户及对应的IP权限(可以不执行,只是一个查看)
mysql > select user,host from mysql.user;

查询master服务的binlog文件名和位置(master节点执行)

mysql > show master status;

  • 日志文件名:mysql-bin.000007
  • 复制的位置:154

slave进行关联master节点(slave节点执行)

  • 进入到slave节点:
mysql > mysql -uroot -p你slave的密码

开始绑定

mysql> change master to master_host='master服务器ip', master_user='root', master_password='master密码', master_port=3306, master_log_file='mysql-bin.000007',master_log_pos=154;

这里注意一下 master_log_file 和 master_log_pos 都是通过 master服务器通过show master status获得。

启动主从复制

mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

再查看主从同步状态

mysql> show slave status\G;

其他命令 (slave节点执行)

# 停止复制
mysql> stop slave;

常见错误排除

主从复制Connecting问题

使用start slave开启主从复制过程后,如果SlaveIORunning一直是Connecting,则说明主从复制一直处于连接状态,这种情况一般是下面几种原因造成的,我们可以根据 Last_IO_Error提示予以排除。

  1. 网络不通 检查ip,端口
  2. 密码不对 检查是否创建用于同步的用户和用户密码是否正确
  3. pos不对 检查Master的 Position

MYSQL镜像服务器因错误停止的恢复 —Slave_SQL_Running: No

@先stop slave,然后执行了一下提示的语句,再
 > stop slave;
 > set global sql_slave_skip_counter=1;
 > start slave; 
 > show slave status\G ;

在二进制日志索引文件中找不到第一个日志文件的名称问题

可以
stop slave;
reset slave;
start slave;

serer-id重复问题

解决方案

测试

Sharding-JDBC整合SpringBoot实现读写分离

数据库


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
  `password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `sex` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '性别',
  `birthday` datetime(0) NOT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (-645894142, 'zhangsan2019312865', '1234567', '男', '1988-12-03 00:00:00');
INSERT INTO `user` VALUES (-129994750, 'zhangsan1198794004', '1234567', '男', '1988-12-03 00:00:00');
INSERT INTO `user` VALUES (1, '张三', '123456', '男', '2001-01-01 00:00:00');

SET FOREIGN_KEY_CHECKS = 1;

引入相关sharding依赖、ssm依赖、数据库驱动

 <!-- 依赖web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--依赖lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--依赖sharding-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <!--依赖数据源druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.7</version>
        </dependency>

yaml

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
      names: ds1,ds2,ds3
      # 给master-ds1每个数据源配置数据库连接信息
      ds1:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://81.69.198.183/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds3-slave
      ds3:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://81.69.198.183/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把两个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds1
    # 配置数据源的读写分离,但是数据库一定要做主从复制
    masterslave:
      # 配置主从名称,可以任意取名字
      name: ms
      # 配置主库master,负责数据的写入
      master-data-source-name: ds1
      # 配置从库slave节点
      slave-data-source-names: ds2,ds3
      # 配置slave节点的负载均衡均衡策略,采用轮询机制
      load-balance-algorithm-type: round_robin
# 整合mybatis的配置XXXXX
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.shardingjdbc.entity

UserMapper

package com.shardingjdbc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shardingjdbc.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

UserService

package com.shardingjdbc.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.shardingjdbc.entity.User;

public interface UserService  extends IService<User> {

}

UserServiceImpl

package com.shardingjdbc.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shardingjdbc.entity.User;
import com.shardingjdbc.mapper.UserMapper;
import com.shardingjdbc.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl  extends ServiceImpl<UserMapper, User> implements UserService{


}

user

package com.shardingjdbc.entity;
import lombok.Data;

@Data
public class User {
    // 主键
    private Integer id;
    // 昵称
    private String nickname;
    // 密码
    private String password;
    // 性
    private String sex;
    // 性
    private String birthday;
}

UserControler

package com.shardingjdbc.controller;
import com.shardingjdbc.entity.User;
import com.shardingjdbc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Random;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/save")
    public String insert() {
        User user = new User();
        user.setNickname("zhangsan"+ new Random().nextInt());
        user.setPassword("1234567");
        user.setSex("男");
        user.setBirthday("1988-12-03");
        userService.save(user);
        return "success";
    }

    @GetMapping("/listuser")
    public List<User> listuser() {
        return userService.list();
    }
}

Props的其他相关配置

acceptor.size: # accept连接的线程数量,默认为cpu核数2倍
executor.size: #工作线程数量最大,默认值: 无限制
max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1
check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false
proxy.frontend.flush.threshold: # proxy的服务时候,对于单个大查询,每多少个网络包返回一次
proxy.transaction.type: # 默认LOCAL,proxy的事务模型 允许LOCAL,XA,BASE三个值,LOCAL无分布式事务,XA则是采用atomikos实现的分布式事务 BASE目前尚未实现
proxy.opentracing.enabled: # 是否启用opentracing
proxy.backend.use.nio: # 是否采用netty的NIO机制连接后端数据库,默认False ,使用epoll机制
proxy.backend.max.connections: # 使用NIO而非epoll的话,proxy后台连接每个netty客户端允许的最大连接数量(注意不是数据库连接限制) 默认为8
proxy.backend.connection.timeout.seconds: #使用nio而非epoll的话,proxy后台连接的超时时间,默认60s

ShardingJdbc分库分表

MySQL分库分表原理

为什么要分库分表

一般的机器(4核16G),单库的MySQL并发(QPS+TPS)超过了2k,系统基本就完蛋了。最好是并发量控制在1k左右。这里就引出一个问题,为什么要分库分表?

分库分表目的:解决高并发,和数据量大的问题。

1、高并发情况下,会造成IO读写频繁,自然就会造成读写缓慢,甚至是宕机。一般单库不要超过2k并发,NB的机器除外。 2、数据量大的问题。主要由于底层索引实现导致,MySQL的索引实现为B+TREE,数据量其他,会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制64TB。

要解决上述问题。最常见做法,就是分库分表。 分库分表的目的,是将一个表拆成N个表,就是让每个表的数据量控制在一定范围内,保证SQL的性能。 一个表数据建议不要超过500W。

分库分表

又分为垂直拆分和水平拆分。

**水平拆分:**统一个表的数据拆到不同的库不同的表中。可以根据时间、地区、或某个业务键维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致。

**垂直拆分:**就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址、订单商品、订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表。

不停机分库分表数据迁移

一般数据库的拆分也是有一个过程的,一开始是单表,后面慢慢拆成多表。那么我们就看下如何平滑的从MySQL单表过度到MySQL的分库分表架构。 1、利用mysql+canal做增量数据同步,利用分库分表中间件,将数据路由到对应的新表中。 2、利用分库分表中间件,全量数据导入到对应的新表中。 3、通过单表数据和分库分表数据两两比较,更新不匹配的数据到新表中。 4、数据稳定后,将单表的配置切换到分库分表配置上。

实现分库分表(1)

<!-- 依赖web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--依赖lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--依赖sharding-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <!--依赖数据源druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.7</version>
        </dependency>

新建数据库goods_db_1和goods_db_2数据库,插入一下两个一样的表

CREATE TABLE goods_1(
    gid INT(20) PRIMARY key COMMENT "商品id,主键",
		gname VARCHAR(50) NOT NULL COMMENT "商品名称",
		user_id INT(20) NOT NULL COMMENT "用户id",
		gstatus VARCHAR(10) NOT NULL COMMENT "商品状态"
)

CREATE TABLE goods_2(
    gid INT(20) PRIMARY key COMMENT "商品id,主键",
		gname VARCHAR(50) NOT NULL COMMENT "商品名称",
		user_id INT(20) NOT NULL COMMENT "用户id",
		gstatus VARCHAR(10) NOT NULL COMMENT "商品状态"
)

application.properties

# 配置Sharding-JDBC的分片策略
# 配置数据源,给数据源起名g1,g2...此处可配置多数据源
spring.shardingsphere.datasource.names=g1,g2

# 配置允许一个实体类映射多张表
spring.main.allow-bean-definition-overriding=true

# 配置数据源具体内容————————包含  连接池,  驱动,             地址,   用户名,    密码
#                                 g1配置g1.type,g1.driver-class-name,g1.url,g1.username,g1.password
spring.shardingsphere.datasource.g1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.g1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.g1.url=jdbc:mysql://120.25.213.140/goods_db_1?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
spring.shardingsphere.datasource.g1.username=root
spring.shardingsphere.datasource.g1.password=root

# 配置数据源具体内容————————包含  连接池,  驱动,             地址,   用户名,    密码
#                                 g2配置g2.type,g2.driver-class-name,g2.url,g2.username,g2.password
spring.shardingsphere.datasource.g2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.g2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.g2.url=jdbc:mysql://120.25.213.140/goods_db_2?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
spring.shardingsphere.datasource.g2.username=root
spring.shardingsphere.datasource.g2.password=root

# 配置数据库的分布,表的分布
# m1:goods-db_1 goods_db_2; m2:goods_1,goods_2;
spring.shardingsphere.sharding.tables.goods.actual-data-nodes=g$->{1..2}.goods_$->{1..2}

# 指定goods表 主键gid 生成策略为 SNOWFLAKE
spring.shardingsphere.sharding.tables.goods.key-generator.column=gid
spring.shardingsphere.sharding.tables.goods.key-generator.type=SNOWFLAKE

# 指定数据库分片策略 约定user_id值是偶数添加到goods_db_1中,奇数添加到goods_db_2中
spring.shardingsphere.sharding.tables.goods.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.goods.database-strategy.inline.algorithm-expression=g$->{user_id % 2 + 1}

# 指定表分片策略 约定gid值是偶数添加到goods_1表,如果gid是奇数添加到goods_2表
spring.shardingsphere.sharding.tables.goods.table-strategy.inline.sharding-column=gid
spring.shardingsphere.sharding.tables.goods.table-strategy.inline.algorithm-expression=goods_$->{gid % 2 + 1}

# 打开sql输出日志
spring.shardingsphere.props.sql.show=true

实体类

@Data
public class Goods {
    private Integer gid;
    private String gname;
    private Integer userId;
    private String gstatus;
}

GoodsMapper

package com.shardingjdbc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shardingjdbc.entity.Goods;
import org.springframework.stereotype.Repository;

@Repository
public interface GoodsMapper extends BaseMapper<Goods> {

}

启动类

@SpringBootApplication
@MapperScan("com.shardingjdbc.mapper")
public class ShardingJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }

}

测试类

@SpringBootTest
class ShardingJdbcApplicationTests {

    @Autowired
    GoodsMapper goodsMapper;

    @Test
    void addGoods() {
        Goods good = new Goods();
        good.setGid(5);
        good.setGname("华为手机");
        good.setUserId(100);
        good.setGstatus("已发布");
        goodsMapper.insert(good);
    }
}

实现分库分表(2)

分库分表的方式

**水平拆分:**统一个表的数据拆到不同的库不同的表中。可以根据时间、地区、或某个业务键维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致。

**垂直拆分:**就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址、订单商品、订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表。

逻辑表

逻辑表是指:水平拆分的数据库或者数据表的相同路基和数据结构表的总称。比如用户数据根据用户id%2拆分为2个表,分别是:user0和user1。他们的逻辑表名是:user。 在shardingjdbc中的定义方式如下:

spring:
  shardingsphere:
    sharding:
      tables:
        # user 逻辑表名
        user:

分库分表数据节点 - actual-data-nodes

tables:
        # user 逻辑表名
        ksd_user:
          # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 相同表
          actual-data-nodes: ds$->{0..2}.user$->{0..1}
           # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 不同表
          actual-data-nodes: ds0.user$->{0..1},ds1.user$->{2..4}
          # 指定单数据源的配置方式
          actual-data-nodes: ds0.user$->{0..4}
          # 全部手动指定
          actual-data-nodes: ds0.user0,ds1.user0,ds0.user1,ds1.user1,

数据分片是最小单元。由数据源名称和数据表组成,比如:ds0.user0。

分库分表规则

分库分表5种分片策略

数据源分片分为两种:

  • 数据源分片
  • 表分片

这两个是不同维度的分片规则,但是它们额能用的分片策略和规则是一样的。它们由两部分构成:

  • 分片键
  • 分片算法

第一种:none

对应NoneShardingStragey,不分片策略,SQL会被发给所有节点去执行,这个规则没有子项目可以配置。

第二种:inline 行表达时分片策略(核心,必须要掌握)

对应InlineShardingStragey。使用Groovy的表达时,提供对SQL语句种的=和in的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开放,如:user${分片键(数据表字段)id % 5} 表示user表根据某字段(id)模 5.从而分为5张表,表名称为:user0到user4 。如果库也是如此。

实战

数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user0
-- ----------------------------
DROP TABLE IF EXISTS `user0`;
CREATE TABLE `user0`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
  `password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `sex` int(2) NOT NULL COMMENT '性别',
  `birthday` datetime(0) NOT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for user1
-- ----------------------------
DROP TABLE IF EXISTS `user1`;
CREATE TABLE `user1`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
  `password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `age` int(11) NOT NULL COMMENT '年龄',
  `sex` int(2) NOT NULL COMMENT '性别',
  `birthday` datetime(0) NOT NULL COMMENT '出生日期',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

pom

<!-- 依赖web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--依赖lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--依赖sharding-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <!--依赖数据源druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.7</version>
        </dependency>

application.yml

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: sub #激活sub

application-sub.yml

server:
  port: 8085
  # 配置允许一个实体类映射多张表
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds1任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds1-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds0
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # user 逻辑表名
        user:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.user$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              sharding-column: sex    # 分片字段(分片键)
              algorithm-expression: ds$->{sex % 2} # 分片算法表达式
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: user$->{age % 2} # 分片算法表达式
 #需求:我对用户1000w的数据,对用户表的数据进行分表和分库的操作。根据年龄单数存储在user1  偶数ksd_user0
 #同时age单数存储在dsl偶数ds0中

UserMapper

@Repository
public interface UserMapper extends BaseMapper<User> {


}

实体类

@Data
public class User {
    // 主键
    private Integer id;
    // 昵称
    private String nickname;
    // 密码
    private String password;
    // 性
    private Integer sex;

    private Integer age;
    // 性
    private Date birthday;
}

启动类

@SpringBootApplication
@MapperScan("com.shardingjdbc.mapper")
public class ShardingJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }

}

测试类

 @Autowired
    UserMapper userMapper;

    @Test
    void addGoods() {
        User user = new User();
        user.setNickname("zhangsan"+ new Random().nextInt());
        user.setPassword("123");
        user.setSex(1);
        user.setAge(20);
        user.setBirthday(new Date());
        userMapper.insert(user);
    }

只分表,不分库配置

# 配置默认数据源ds0
sharding:
  # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
  default-data-source-name: ds0
  # 配置分表的规则
  tables:
    # user 逻辑表名
    user:
      # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
      actual-data-nodes: ds0.user$->{0..1}    #ds0.user$->{0..10} 代表有11个表 
      # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
      table-strategy:
        inline:
          sharding-column: age    # 分片字段(分片键)
          algorithm-expression: user$->{age % 2} # 分片算法表达式  # user$->{age % 11}  

只分库,不分表

 # 配置默认数据源ds0
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # user 逻辑表名
        user:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.user0  #ds$->{0..4}.user0  代表有5个数据库
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              sharding-column: sex    # 分片字段(分片键)
              algorithm-expression: ds$->{sex % 2} # 分片算法表达式   #ds$->{sex % 5}

根据实时间日期 - 按照标准规则分库分表

标准分片 - Standard(了解)

  • 对应StrandardShardingStrategy.提供对SQL语句中的=,in和恶between and 的分片操作支持。
  • StrandardShardingStrategy只支持但分片键。提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。
  • PreciseShardingAlgorithm是必选的呃,用于处理=和IN的分片
  • 和RangeShardingAlgorithm是可选的,是用于处理Betwwen and分片,如果不配置和RangeShardingAlgorithm,SQL的Between AND 将按照全库路由处理。

application.yml

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: date #激活date

application-date.yml

server:
  port: 8085
  # 配置允许一个实体类映射多张表
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds1任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds1-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/stundetdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds0
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # user 逻辑表名
        user:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.user$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            standard:
              shardingColumn: birthday
              preciseAlgorithmClassName: com.shardingjdbc.config.BirthdayAlgorithm
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: user$->{age % 2} # 分片算法表达式

BirthdayAlgorithm.java

package com.shardingjdbc.config;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.*;

public class BirthdayAlgorithm implements PreciseShardingAlgorithm<Date> {
    List<Date> dateList = new ArrayList<>();
    {
        //数据源有几个这里就配几个,这里数据源只有两个,所有配两个即可
        Calendar calendar2 = Calendar.getInstance();
        calendar2.set(2021, 1, 1, 0, 0, 0);
        Calendar calendar3 = Calendar.getInstance();
        calendar3.set(2022, 1, 1, 0, 0, 0);
        dateList.add(calendar2.getTime());
        dateList.add(calendar3.getTime());
    }
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {
        // 获取属性数据库的值
        Date date = preciseShardingValue.getValue();
        // 获取数据源的名称信息列表
        Iterator<String> iterator = collection.iterator();
        String target = null;
        for (Date s : dateList) {
            target = iterator.next();
            // 如果数据晚于指定的日期直接返回
            if (date.before(s)) {
                break;
            }
        }
        return target;
    }
}

启动类

@SpringBootApplication
@MapperScan("com.shardingjdbc.mapper")
public class ShardingJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }

}

测试类

    @Test
    void addGoods() {
        User user = new User();
        user.setNickname("zhangsan"+ new Random().nextInt());
        user.setPassword("12333333");
        user.setSex(1);
        user.setAge(20);
        user.setBirthday(new Date());
        userMapper.insert(user);
    }

分布式主键策略

ShardingSphere提供灵活的配置分布式主键生成策略方式。在分片规则配置模块克配置每个表的主键生成策略。默认使用雪花算法。(snowflake)生成64bit的长整型数据。支持两种方式配置

  • SNOWFLAKE
  • UUID

这里切记:主键列不能自增长。数据类型是:bigint(20)

spring:
  shardingsphere:
    sharding:
      tables:
        # ksd_user 逻辑表名
        ksd_user:
          key-generator:
              # 主键的列明,
            column: userid
            type: SNOWFLAKE  #雪花算法

ShardingJDBC - 分库分表 - 年月案例

准备6张表两个数据库

DROP TABLE IF EXISTS `ksd_user_order_202101`;
CREATE TABLE `ksd_user_order_202101`  (
  `orderId` int(30) NOT NULL COMMENT '主键id',
  `userId` int(20) NOT NULL COMMENT '用户id',
  `ordernumber` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `createTime` datetime(0) NOT NULL,
  `yearmonth` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  PRIMARY KEY (`orderId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

依赖

   <!-- 依赖web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--依赖lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--依赖sharding-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <!--依赖数据源druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.7</version>
        </dependency>

application.yml

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: date #激活date

application-date.yml

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds1任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://120.25.213.140/ksd_order_db?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds1-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://81.69.198.183/ksd_order_db?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds0
    sharding:
      # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds1
      # 配置分表的规则
      tables:
         ksd_user_order:

          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
           actual-data-nodes: ds$->{0..1}.ksd_user_order_$->{2021..2022}${(1..3).collect{t ->t.toString().padLeft(2,'0')}}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
           database-strategy:
             inline:
               sharding-column: userId    # 分片字段(分片键)
               algorithm-expression: ds$->{userId % 2} # 分片算法表达式
           table-strategy:
#              inline:
#                shardingColumn: yearmonth
#                algorithmExpression: ksd_user_order_$->{yearmonth}
            standard:
              shardingColumn: yearmonth
              preciseAlgorithmClassName: com.shardingjdbc.config.YearMonthShardingAlgorithm

OrderMapper

@Repository
public interface OrderMapper extends BaseMapper<KsdUserOrder>{

}

KsdUserOrder

@Data
public class KsdUserOrder {
    // 主键
    @TableId(value = "orderId",type = IdType.ASSIGN_ID)
    private Integer orderId;
    // 订单编号
    private String ordernumber;
    // 用户ID
    @TableField(value = "userId")
    private Integer userId;

    private String yearmonth;
    // 创建时间
    @TableField(value = "createTime")
    private Date createTime;
}

YearMonthShardingAlgorithm

package com.shardingjdbc.config;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import java.util.Collection;

public class YearMonthShardingAlgorithm implements PreciseShardingAlgorithm<String> {
    private static final String SPLITTER = "_";
    @Override
    public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
        String tbName = shardingValue.getLogicTableName() + "_" + shardingValue.getValue();
        System.out.println("Sharding input:" + shardingValue.getValue() + ", output:{}" + tbName);
        return tbName;
    }
}

启动类

@SpringBootApplication
@MapperScan("com.shardingjdbc.mapper")
public class ShardingJdbcApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }
}

测试类

    @Autowired
    private OrderMapper userOrderMapper;
    @Test
    public void orderyearMaster() {
        KsdUserOrder userOrder = new KsdUserOrder();
        userOrder.setUserId(1111);
        userOrder.setCreateTime(new Date());
        userOrder.setOrdernumber("133455678");
        userOrder.setYearmonth("202201");
        userOrder.setUserId(2);
        userOrderMapper.insert(userOrder);
    }

ShardingJdbc事物管理

分布式式事务的应用和实践

官方地址:https://shardingsphere.apache.org/document/legacy/4.x/document/cn/features/transaction/function/base-transaction-seata/

https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/usage/transaction/

数据库事务需要满足ACID(原子性、一致性、隔离性、持久性)四个特性。

  • 原子性(Atomicity)指事务作为整体来执行,要么全部执行,要么全不执行。
  • 一致性(Consistency)指事务应确保数据从一个一致的状态转变为另一个一致的状态。
  • 隔离性(Isolation)指多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability)指已提交的事务修改数据会被持久保存。

在单一数据节点中,事务仅限于对单一数据库资源的访问控制,称之为本地事务。几乎所有的成熟的关系型数据库都提供了对本地事务的原生支持。 但是在基于微服务的分布式应用环境下,越来越多的应用场景要求对多个服务的访问及其相对应的多个数据库资源能纳入到同一个事务当中,分布式事务应运而生。

关系型数据库虽然对本地事务提供了完美的ACID原生支持。 但在分布式的场景下,它却成为系统性能的桎梏。如何让数据库在分布式场景下满足ACID的特性或找寻相应的替代方案,是分布式事务的重点工作。

本地事务

在不开启任何分布式事务管理器的前提下,让每个数据节点各自管理自己的事务。 它们之间没有协调以及通信的能力,也并不互相知晓其他数据节点事务的成功与否。 本地事务在性能方面无任何损耗,但在强一致性以及最终一致性方面则力不从心。

两阶段提交

XA协议最早的分布式事务模型是由X/Open国际联盟提出的X/Open Distributed Transaction Processing(DTP)模型,简称XA协议。

基于XA协议实现的分布式事务对业务侵入很小。 它最大的优势就是对使用方透明,用户可以像使用本地事务一样使用基于XA协议的分布式事务。 XA协议能够严格保障事务ACID特性。

严格保障事务ACID特性是一把双刃剑。 事务执行在过程中需要将所需资源全部锁定,它更加适用于执行时间确定的短事务。 对于长事务来说,整个事务进行期间对数据的独占,将导致对热点数据依赖的业务系统并发性能衰退明显。 因此,在高并发的性能至上场景中,基于XA协议的分布式事务并不是最佳选择。

柔性事务

如果将实现了ACID的事务要素的事务称为刚性事务的话,那么基于BASE事务要素的事务则称为柔性事务。 BASE是基本可用、柔性状态和最终一致性这三个要素的缩写。

基本可用(Basically Available)保证分布式事务参与方不一定同时在线。 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。 而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。

在ACID事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。 柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。

基于ACID的强一致性事务和基于BASE的最终一致性事务都不是银弹,只有在最适合的场景中才能发挥它们的最大长处。 可通过下表详细对比它们之间的区别,以帮助开发者进行技术选型。

导入分布式事务的依赖

 <!--依赖sharding-->
 <dependency>
     <groupId>io.shardingsphere</groupId>
     <artifactId>sharding-transaction-spring-boot-starter</artifactId>
     <version>3.1.0</version>
 </dependency>

事务的几种类型

- 本地事务

  • 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中。
  • 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能回滚。
  • 不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交。

两阶段XA事务

  • 支持数据分片后的跨库XA事务
  • 两阶段提交保证操作的原子性和数据的强一致性
  • 服务宕机重启后,提交/回滚中的事务可自动恢复
  • SPI机制整合主流的XA事务管理器,默认Atomikos,可以选择使用Narayana和Bitronix
  • 同时支持XA和非XA的连接池
  • 提供spring-boot和namespace的接入端

不支持:

  • 服务宕机后,在其它机器上恢复提交/回滚中的数据

Seata柔性事务

  • 完全支持跨库分布式事务
  • 支持RC隔离级别
  • 通过undo快照进行事务回滚
  • 支持服务宕机后的,自动恢复提交中的事务

依赖:

  • 需要额外部署Seata-server服务进行分支事务的协调 待优化项
  • ShardingSphere和Seata会对SQL进行重复解析
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @ShardingTransactionType(TransactionType.XA)
    @Transactional(rollbackFor = Exception.class)
    public int saveUserOrder(KsdUserOrder ksdUserOrder) {
       orderMapper.insert(ksdUserOrder);
        //int a = 1/0; //测试回滚,统一提交的话,将这行注释掉就行
        return 1;
    }
}

测试类

@SpringBootTest
class ShardingJdbcApplicationTests {
    @Autowired
    private OrderService userOrderMapper;
    @Test
    public void orderyearMaster() {
        KsdUserOrder userOrder = new KsdUserOrder();
        userOrder.setUserId(1111);
        userOrder.setCreateTime(new Date());
        userOrder.setOrdernumber("133455678");
        userOrder.setYearmonth("202201");
        userOrder.setUserId(2);
        userOrderMapper.saveUserOrder(userOrder);
    }
}
avatar

yuanyourdomain

写代码,做研究,记录生活。

RECOMMENDED

MyBatis 动态 SQL

2026-07-08

Nginx 基础入门

2026-07-08

Maven 多模块与私服

2026-07-08