分库分表—ShardingJDBC
简介
1、ShardingJDBC简介
官方中文文档:概览 :: ShardingSphere
定位为轻量级 Java框架,在 Java 的 JDBC层提供的额外服务,它使用客户端直连数据库,以 jar包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC驱动,完全兼容 JDBC 和各种 ORM框架。
-
适用于任何基于 JDBC 的 ORM框架,如:JPA、Hibernate、Mybatis、Spring JDBC Template 或是直接使用 JDBC;
-
支持任何第三方的数据库连接池,如:DBCP、C3P0、BoneCP、HikariCP 等;
-
支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

1.1、逻辑表、真实表
-
逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例如:订单数据根据主键尾数拆分为 10张表,分别是 t_order_0 到 t_order_9,他们的逻辑表名都为 t_order
-
真实表:在水平拆分的数据库中真实存在的物理表。就是 t_order_0 到 t_order_9。
2、简单使用
2.1、实践1 - 分库写入
将数据按照规则分库写入进去。
2.1.1、引入依赖
<dependencies> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies>
2.1.2、准备数据库环境
需要两个相同库表结构的数据库,最好是两个主机的 MySQL数据库。我这里是同一个主机的两个数据库。
如果是云服务的话,需要开放外网连接:
update user set host = '%' where user = 'root'; FLUSH PRIVILEGES;
2.1.3、pojo、mapper、service、serviceImpl
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String password;
}
public interface UserMapper extends BaseMapper<User> {}
public interface UserService extends IService<User> {}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
2.1.4、修改配置文件
推荐查看官方文档。
server:
port: 8888
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
names: db0,db1
# 为每个数据源单独进行配置
db0:
# 数据源实现类,这里使用默认的 HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
username: root
password: 123456
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
username: root
password: 123456
rules:
sharding:
tables:
#这里填写表名称,程序中对这张表的所有操作,都会先采用下面的路由方案
user:
#这里填写实际的路由节点,比如现在我们要分两个库,那么就可以把两个库都写上,以及对应的表
#也可以使用表达式,比如下面的可以简写为 db$->{0..1}.user
actual-data-nodes: db0.user,db1.user
#这里是分库策略配置
database-strategy:
#这里选择标准策略,也可以配置复杂策略 complex,基于多个键进行分片
standard:
#参与分片运算的字段,下面的算法会根据这里提供的字段进行运算
# 就是取模算法,为 0 就放 db0,为 1 就放 db1
sharding-column: id
#这里填写我们下面自定义的算法名称
sharding-algorithm-name: my-alg
sharding-algorithms:
#自定义一个新的算法,名称随意
my-alg:
#算法类型,官方内置了很多种,这里演示最简单的一种
type: MOD
props:
sharding-count: 2
props:
#开启日志,一会方便我们观察
sql-show: true
2.1.5、分库插入测试
@SpringBootTest
class ShardingJdbcTestApplicationTest {
@Resource
UserService userService;
@Test
void batchInsertTest() {
for (int i = 0; i < 9; i++) {
userService.save(new User(i, "user-" + i, "password-" + i));
}
}
}
2.1.5.1、执行语句日志
ShardingJDBC 会把 MyBatis-Plus 要去执行的 SQL语句当作逻辑SQL 进行分析,然后修改后再执行。依次分为 Logic SQL、SQLStatement、Actual SQL。
2022-07-21 17:50:02.873 INFO 8552 --- [ main] com.chw.ShardingJdbcTestApplicationTest : Started ShardingJdbcTestApplicationTest in 4.937 seconds (JVM running for 6.572) ShardingSphere-SQL:Logic SQL:INSERT INTO user (id,name,password) VALUES (?,?,?) ShardingSphere-SQL:SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL:Actual SQL: db0 ::: INSERT INTO user (id,name,password ) VALUES (?, ?, ?) ::: [0, user-0, password-0] ShardingSphere-SQL : Logic SQL: INSERT INTO user ( id, name, password ) VALUES ( ?, ?, ? ) 2022-07-21 17:50:04.133 INFO 8552 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-21 17:50:04.133 INFO 8552 --- [ main] ShardingSphere-SQL : Actual SQL: db1 ::: INSERT INTO user ( id, name, password ) VALUES (?, ?, ?) ::: [1, user-1, password-1]
2.1.5.2、数据库结果


2.2、实践2 - 分表写入
将数据按照规则写入同一个数据库的不同表中。
2.2.1、准备数据库环境
创建 user_0、user_1。

2.2.2、修改配置文件
虽然我们想把数据分表写入 user_0、user_1,但是我们不需要修改业务代码,因为这两个表的逻辑表名称都是 user,所以只需要在逻辑上给 user表插入数据即可,ShardingJDBC 会在物理上根据规则进行分表。
server:
port: 8888
spring:
application:
name: sharding-jdbc
shardingsphere:
datasource:
# 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式
names: db0,db1
# 为每个数据源单独进行配置
db0:
# 数据源实现类,这里使用默认的 HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1
username: root
password: 123456
db1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2
username: root
password: 123456
rules:
sharding:
tables:
user:
# 也可以写成 db0.user0,db0.user1
actual-data-nodes: db0.user_$->{0..1}
#现在我们来配置一下分表策略,之前是 database-strategy
table-strategy:
#基本都跟之前是一样的
standard:
sharding-column: id
sharding-algorithm-name: my-alg
sharding-algorithms:
my-alg:
#这里我们演示一下INLINE方式,我们可以自行编写表达式来决定
type: INLINE
props:
#比如我们还是希望进行模2计算得到数据该去的表
#只需要给一个最终的表名称就行了 user_,后面的数字是表达式取模算出的
#实际上这样写和 MOD模式一模一样
algorithm-expression: user_$->{id % 2}
#查询会根据分片策略进行,但范围查询会无视分片策略,所以要改成 true,
# 让范围查询支持分片策略
allow-range-query-with-inline-sharding: true
props:
#开启日志,一会方便我们观察
sql-show: true
2.2.3、分表插入测试
测试的代码还是一样的。
2.2.3.1、执行语句日志
会自动把 user表变成 user0 或 user1
ShardingSphere-SQL: Logic SQL: INSERT INTO user ( id,name,password ) VALUES ( ?,?,? ) ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: INSERT INTO user_0 ( id,name,password ) VALUES (?, ?, ?) ::: [0, user-0, password-0] 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO user ( id, name, password ) VALUES ( ?, ?, ? ) 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-21 19:47:19.539 INFO 14084 --- [ main] ShardingSphere-SQL : Actual SQL: db0 ::: INSERT INTO user_1 ( id,name,password ) VALUES (?, ?, ?) ::: [1, user-1, password-1]
2.2.3.2、数据库结果

2.2.4、分表多查询测试
@Test
void batchSelectByIdTest() {
ArrayList<Integer> idList = new ArrayList<>();
idList.add(0);
idList.add(1);
List<User> userList = userService.listByIds(idList);
userList.forEach(System.out::println);
}
2.2.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT id,name,password FROM user WHERE id IN ( ?, ?) ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) ShardingSphere-SQL:Actual SQL:db0:::SELECT id,name,password FROM user_0 WHERE id IN ( ? , ? ) UNION ALL SELECT id,name,password FROM user_1 WHERE id IN ( ? , ? ) ::: [0, 1, 0, 1]
2.2.5、分表范围查询测试
查询会根据分片策略来,但是范围查询会无视分片策略,所以 allow-range-query-with-inline-sharding 要改成 true,让范围查询支持分片策略。
@Test
void rangeSelectByIdTest() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("id",1);
queryWrapper.le("id",7);
List<User> userList = userService.list(queryWrapper);
userList.forEach(System.out::println);
}
2.2.5.1、执行语句日志
ShardingSphere-SQL: Logic SQL: SELECT id,name,password FROM user WHERE (id >= ? AND id <= ?) ShardingSphere-SQL: SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: SELECT id,name,password FROM user_0 WHERE (id >= ? AND id <= ?) UNION ALL SELECT id,name,password FROM user_1 WHERE (id >= ? AND id <= ?) ::: [1, 7, 1, 7]
2.3、实践3 - 雪花算法
2.3.1、主键数据类型改成 bigint
ALTER TABLE `yyds`.`test` MODIFY COLUMN `id` bigint NOT NULL FIRST;
2.3.2、修改配置文件
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: sharding: tables: user: actual-data-nodes: db0.user,db1.user # 分库策略 database-strategy: standard: sharding-column: id sharding-algorithm-name: my-alg # 使用自定义的主键生成策略 key-generate-strategy: column: id key-generator-name: my-gen key-generators: #这里写我们自定义的主键生成算法 my-gen: # 使用雪花算法 type: SNOWFLAKE props: # 工作机器ID,保证唯一就行 worker-id: 666 sharding-algorithms: my-alg: type: MOD props: sharding-count: 2 props: #开启日志,一会方便我们观察 sql-show: true
2.3.3、修改代码
因为我们使用的是 MyBatis-Plus,它提供的方法都是包含全部字段的,如果我们不传值,它就会设置一个默认值,那么 ShardingJDBC 就会以为你已经设置了主键值了,就不需要用雪花算法帮你生产了。所以我们需要自己指定一个 SQL语句。
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Insert("insert into user(name, password) values(#{name}, #{password})")
int addUser(User user);
}
2.3.4、测试
@Resource
UserMapper userMapper;
@Test
void batchInsertWithoutIdTest() {
for (int i = 0; i < 10; i++) {
User user = new User();
user.setName("user-" + i);
user.setPassword("password-" + i);
userMapper.addUser(user);
}
}
2.3.4.1、执行语句日志
ShardingSphere-SQL: Logic SQL: insert into user(name, password) values(?, ?) ShardingSphere-SQL: SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) ShardingSphere-SQL: Actual SQL: db0 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-10, password-10, 757242293794111488] 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : Logic SQL: insert into user(name, password) values(?, ?) 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-07-22 14:10:31.814 INFO 11732 --- [ main] ShardingSphere-SQL : Actual SQL: db1 ::: insert into user(name, password, id) values(?, ?, ?) ::: [user-11, password-11, 757242294175793153]
2.4、实践4 - 读写分离
2.4.1、数据库环境配置
记得创建好数据表。
2.4.1.1、从库改成只读模式
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf # 加入这行代码 read-only = 1 # 重启下 MySQL sudo systemctl restart mysql.service
2.4.1.2、配置主从关系
# 主库 # 进入 MySQL数据库 sudo mysql -u root -p # 展示状态数据 show master status
# 从库 # 进入 MySQL数据库 sudo mysql -u root -p # 根据主库的状态数据配置主从关系 change replication source to SOURCE_HOST='192.168.0.8',SOURCE_USER='test',SOURCE_PASSWORD='123456',SOURCE_LOG_FILE='binlog.000007',SOURCE_LOG_POS=156; # 开启主从关系 start replica;
2.4.2、修改配置文件
server: port: 8888 spring: application: name: sharding-jdbc shardingsphere: datasource: # 有几个数据就配几个,这里是名称,按照下面的格式,名称+数字的形式 names: db0,db1 # 为每个数据源单独进行配置 db0: # 数据源实现类,这里使用默认的 HikariDataSource type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test1 username: root password: 123456 db1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/shardingjdbc_test2 username: root password: 123456 rules: #配置读写分离 readwrite-splitting: data-sources: #这个名称随便写 user-db: #使用静态类型,如果使用动态 Dynamic类型可以自动发现auto-aware-data-source-name type: Static props: #配置写库,即主库(只能一个) write-data-source-name: db0 #配置从库(多个,逗号隔开) read-data-source-names: db1 #读数据的负载均衡策略,可以自定义 load-balancer-name: my-load load-balancers: #自定义的负载均衡策略 my-load: type: ROUND_ROBIN props: #开启日志,一会方便我们观察 sql-show: true
2.4.3、测试
可以插入一个数据,然后再查一下看看是哪个库。
更多推荐


所有评论(0)