分库分表ShardingJDBC最佳实践
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分库分表ShardingJDBC最佳实践相关的知识,希望对你有一定的参考价值。
1 添加依赖
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>$sharding.version</version>
</dependency>
2 分库分表数选择
根据未来两年的业务量,估算两年的业务总量M,单表数据量不能超过N(需要看具体业务场景,字段少的可以适量多一些,可与架构师及部门经验丰富的同事探讨,最大不要超过1000W);
总的分表数量K≥M/N,且K值向上取接近的最小2的次幂。
例如业务总量M=10亿,单表数量N=700W,则M/N≈143,向上取最小的2次幂为:,故总的分表数量为256。
可将分表数设定的尽可能的小,一台服务器存放多个库,业务增长后,磁盘不足时,可将该服务器上的整个库的数据迁移到新的服务器。
如总的分表数量为1024,可分为32个库:db0~db31,每个库分32张表:tb0~tb31,共16台服务器:server0~server15。
具体划分如下:
- server0上有db0库和db16库
- server1上有db1库和db17库
- ……
- server15上有db15库和db31库
如果业务发展一段时间后出现磁盘不足,可在申请16台服务器:server16~server31
- server0上的db16库迁移至server16
- server1上的db17库迁移至server17
- ……
- server15上的db31库迁移至server31
迁移后磁盘释放50%空间
3 分片键(拆分键)选择
分片键的选择一定要具有明显的业务特征,具体表现为:
- 常用的已知的具体值,查询条件中要包含分片键,需要根据分片键确定在哪个库表,如果查询条件里没有分片键,将会遍历所有库表,这种情况是不允许的;
- 分片键分辨度较高,比较分散,不能选择分辨度不高的的字段作为分片键,容易造成数据分布不均匀;
- 分片键不能有偏移,比如分片键某个值不能远远大于其他值,这种情况也会造成数据分布不均匀;
- 大小写,如果分片键在某些表里不区分大小写,那么分片键要统一大写或小写后用于分片,因此其他条件要考虑统一大小写后的情况;
目前项目里使用的分片键为商家编码,并统一为大写,因为:
- 在查询分库分表数据时,商家编码是已知的,所以查询条件中包含商家编码;
- 商家编码较多,能够保证按照商家编码分片后数据基本是均匀分布的;
- 系统中不存在某个或某几个商家编码的数据量占据大部分的数据量;
4 分片算法
inline: Groovy的Inline表达式,可以支持SQL语句中的=和IN的操作,InlineShardingStrategy只支持单分片键;这种方式是最常用的,也是目前项目中在使用的。
其他分片策略参考:Sharding-JDBC分片策略
4.1 分库算法
以32个库为例:Math.abs(库分片键.toUpperCase().hashCode()) % 32,将分片键商家编码统一大写后,取hashCode的绝对值,对32取模,32为分库数;
通用公式为:Math.abs(库分片键.toUpperCase().hashCode()) % 分库数
4.2 分表算法
以每个库32张表为例:(Math.abs(表分片键.toUpperCase().hashCode()) % 1024).intdiv(32),要保证分到某个库的数据,均匀分布到该库的每个表;
注意:此处不能直接用分库的公式,因为某个商家计算后分配到第i库,如果分表用同样的公式,则也只能落到第i表,导致一个库中只有一个表有数据。因此,需要先对一个较大的数取模,该较大的数为分库数分表数,记为m,本例中=3232=1024,将hash后的值分散到0-1023之间,在除以32,则最终结果被分配到0-31之间。
通用公式为:(Math.abs(表分片键.toUpperCase().hashCode()) % 分库数*分表数).intdiv(分库数)
5 全局(分布式)主键
分库分表,要求主键全局唯一,因为存在数据汇总的场景,如存放到ES、大数据等;因此不能使用数据库自增方式,需要一种全局唯一的主键生成方式;
5.1 shardingjdbc提供的主键
1. 添加maven依赖
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-self-id-generator</artifactId>
<version>1.4.2</version>
</dependency>
2. 在@Configuration类中创建Bean对象
@Bean
public IdGenerator getIdGenerator()
return new CommonSelfIdGenerator();
3. 使用
@Autowired
private IdGenerator idGenerator;
@Test
public void generateId()
long id = idGenerator.generateId().longValue();
......
5.2 雪花算法生成主键
很多插件如sharding-jdbc, mybatis-plus提供了雪花算法生成器,目前常用的是这种方式,生产的id时间上基本是有序的,使用很方便。
6 查询条件没有分片键如何处理
不允许没有分片键的查库操作;
查询或更新库操作条件里必须有分片键,否则就需要将分库分表数据存放到ES或建立查询条件与分片键的中间表;
ES或分片键中间表是为个别场景如页面多条件查询,用户获取不到分片键时采取的折中方案,绝大多数场景是能获取到分片键的,如果获取不到则可认为分片键的选取是不合理的,需要根据实际场景调整分片键。
ShardingJdbc-分表;分库分表;读写分离;一主多从+分表;一主多从+分库分表;公共表;数据脱敏;分布式事务
创建项目
一顿下一步,勾选web、lombok等插件
分表
导包
ShardingJdbc
官方最新稳定版4.1.1
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
Mysql
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
Druid
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
MyBatisPlus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
表结构
注意:之前我这表名叫table_0,结果一直报找不到表,后来发现,不能用关键字
CREATE TABLE `master_db`.`my_table_0` (
`id` BIGINT (20) NOT NULL,
`source` VARCHAR (20),
`remark` VARCHAR (100),
PRIMARY KEY (`id`)
);
CREATE TABLE `master_db`.`my_table_1` (
`id` BIGINT (20) NOT NULL,
`source` VARCHAR (20),
`remark` VARCHAR (100),
PRIMARY KEY (`id`)
);
Yml
application-sharding_table.yml
server:
port: 80
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db #库名,只是名字
db: #数据源
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
sharding: #指定表的分片规则
tables: # 看到table "s" ,就知道可配置多个
my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
actualDataNodes: db.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
tableStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: my_table_$->{id % 2} #分片规则
keyGenerator: # 指定表的主键生成策略
type: SNOWFLAKE #SNOWFLAKE
column: id #指定主键
props:
sql:
show: true #输出日志
至此配置完毕,运行效果见下方代码
分库分表
java代码都不改,只改数据库及yml
数据库
master_db、 slave_db1、 slave_db2
库中分别创建my_table_0、my_table_1,结构同 上
Yml
application-sharding_master_slave.yml
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db0,db1,db2 #库名,只是名字
db0: #主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db1: #从库1
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db2: #从库2
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
#分库分表start
sharding: #指定表的分片规则
tables: # 看到table "s" ,就知道可配置多个
my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
actualDataNodes: db$->{0..2}.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
databaseStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: db$->{id % 3} #分片算法,分了三个库,所以要%3
tableStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: my_table_$->{id % 2} #分片算法
keyGenerator: # 指定表的主键生成策略
type: SNOWFLAKE #SNOWFLAKE
column: id #指定主键
#分库分表end
props:
sql:
show: true #输出日志
然后继续访问第一步的save&select方法就可看到效果了,配置文件中写的主从库没有实际意义,起错名字了,实际都会往里面写
读写分离
数据库
模拟主从,不做MySql真正的主从同步
上面做表分片时已经创建好了主库master_db 再创建两个库 slave_db1 slave_db2
三个库表结构一致
master_db表中无数据
slave_db1 表数据如下
"id" "source" "remark"
"1" "source1" "remark1"
slave_db2 表数据如下
"id" "source" "remark"
"1" "source2" "remark2"
Yml
application-master_slave.yml
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: masterdb,slavedb1,slavedb2 #库名,只是名字
masterdb: #主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
slavedb1: #从库1
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
slavedb2: #从库2
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
masterslave:
name: dbs
master-data-source-name: masterdb #主库
slave-data-source-names: slavedb1,slavedb2 #多个从库
load-balance-algorithm-type: ROUND_ROBIN #RANDOM随机 ROUND_ROBIN 轮训
props:
sql:
show: true #输出日志
这时,访问上面搭好的项目,即可进行读写分离,代码见上方链接
其他
只请求主库
在请求上方添加如下代码即可
// HintManager hintManager = HintManager.getInstance();
// hintManager.setMasterRouteOnly();
@GetMapping("select")
public PageInfo select(int pageNum,int pageSize,Long id){
// HintManager hintManager = HintManager.getInstance();
// hintManager.setMasterRouteOnly();
PageHelper.startPage(pageNum,pageSize);
QueryWrapper<Table0> queryWrapper=new QueryWrapper();
if(!StringUtils.isEmpty(id)) {
queryWrapper.eq("id", id);
}
List<Table0> list = table0Service.list(queryWrapper);
PageInfo pageInfo = new PageInfo<>(list);
return pageInfo;
}
读写分离判断逻辑代码
大致的流程是shardingJdbc根据sql获得对应的sqlStatement,然后只有SelectStatement的才走从库
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.masterslave.route.engine.impl;
import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.api.hint.HintManager;
import org.apache.shardingsphere.core.rule.MasterSlaveRule;
import org.apache.shardingsphere.sql.parser.sql.statement.SQLStatement;
import org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement;
import java.util.ArrayList;
/**
* Data source router for master-slave.
*/
@RequiredArgsConstructor
public final class MasterSlaveDataSourceRouter {
private final MasterSlaveRule masterSlaveRule;
/**
* Route.
*
* @param sqlStatement SQL statement
* @return data source name
*/
public String route(final SQLStatement sqlStatement) {
if (isMasterRoute(sqlStatement)) {
MasterVisitedManager.setMasterVisited();
return masterSlaveRule.getMasterDataSourceName();
}
return masterSlaveRule.getLoadBalanceAlgorithm().getDataSource(
masterSlaveRule.getName(), masterSlaveRule.getMasterDataSourceName(), new ArrayList<>(masterSlaveRule.getSlaveDataSourceNames()));
}
private boolean isMasterRoute(final SQLStatement sqlStatement) {
return containsLockSegment(sqlStatement) || !(sqlStatement instanceof SelectStatement) || MasterVisitedManager.isMasterVisited() || HintManager.isMasterRouteOnly();
}
private boolean containsLockSegment(final SQLStatement sqlStatement) {
return sqlStatement instanceof SelectStatement && ((SelecStatement) sqlStatement).getLock().isPresent();
}
}
一主多从+分表
上面实现了一主多从和表分片,现在把它整合到一起
java代码还是没改,主要改的还是Yml
Yml
application-sharding_db_table_oneMaster_slaves.yml
关键配置见 masterSlaveRule,分片配置未改
#一主多从+表分片
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db0,db1,db2 #库名,只是名字
db0: #主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db1: #从库1
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db2: #从库2
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
masterSlaveRule: # 这里配置这个规则的话,相当于是全局读写分离配置
name: ds_rw # 名称,合法的字符串即可,但如果涉及到在读写分离的基础上设置分库分表,则名称需要有意义才可以,另外,虽然目前没有强制要求,但主从库配置需要配置在实际关联的主从库上,如果配置的数据源之间主从是断开的状态,那么可能会发生写入的数据对于只读会话无法读取到的问题
# 如果一个会话发生了写入并且没有提交(显式打开事务),sharidng sphere在后续的路由中,select都会在主库执行,直到会话提交
masterDataSourceName: db0 # 主库的DataSource名称
slaveDataSourceNames: # 从库的DataSource列表,至少需要有一个
- db1
- db2
loadBalanceAlgorithmType: ROUND_ROBIN #从库负载均衡算法类型,可选值:ROUND_ROBIN,RANDOM。若loadBalanceAlgorithmClassName存在则忽略该配置,默认为ROUND_ROBIN
sharding: #指定表的分片规则
tables: # 看到table "s" ,就知道可配置多个
my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
actualDataNodes: db$->{0..2}.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
databaseStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: db$->{id % 3} #分片算法,分了三个库,所以要%3
tableStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: my_table_$->{id % 2} #分片算法
keyGenerator: # 指定表的主键生成策略
type: SNOWFLAKE #SNOWFLAKE
column: id #指定主键
props:
sql:
show: true #输出日志
一主多从+分库分表
上面实现了一主多从和表分片,现在把它整合到一起
java代码还是没改,主要改的还是Yml
创建了两个主库两个从库master_db1、master_db2、slave_db1、slave_db2
下方配置为 master_db1与slave_db1为主从关系、master_db2与slave_db2为主从关系
修改点有点多,查看标*****的位置
Yml
application-sharding_db_table_masters_slaves.yml
#一主多从+表分片
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db0,db1,db2,db3 #库名,只是名字 ******************新增名称db3
db0: #主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db1: #主库1 ****************** 添加主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db2: #从库1
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db3: #从库2 ******************添加从库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
sharding:
master-slave-rules: # 新增配置 ******************
db0: # 此处名字一定要配置和下面 master-data-source-name 名字一致的
master-data-source-name: db0
slave-data-source-names: db2
db1: # 此处名字一定要配置和下面 master-data-source-name 名字一致的
master-data-source-name: db1
slave-data-source-names: db3
tables: # 看到table "s" ,就知道可配置多个
my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
actualDataNodes: db$->{0..1}.my_table_$->{0..1} #******************每组有一主一从,所以是0..1
databaseStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: db$->{(id / 10).toBigInteger() % 2} # *******不能再用id%2方式了,会导致数据分配不均,所以需要自定义方法
tableStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: my_table_$->{id % 2} #分片算法
keyGenerator: # 指定表的主键生成策略
type: SNOWFLAKE #SNOWFLAKE
column: id #指定主键
props:
sql:
show: true #输出日志
公共表&数据脱敏
公共表
项目中会有很多字典表、配置表等等,此类表数据固定、量级不大,所以可以无需采用分库分表,每个库存一份;
当配置公共表后,每次对公共表进行操作,都会同步操作所有库中的公共表
数据库
分别在三个库中新增表pub_table
Java代码
生成pub_table相关的MybatisPlus代码
Yml
application-publicTable_encryptor.yml
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db0,db1,db2 #库名,只是名字
db0: #主库
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db1: #从库1
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db1?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
db2: #从库2
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/slave_db2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
sharding:
default-data-source-name: db #如果有多个库,公共表只有一个库有,那么,就要指定有公共表的库,否则会报找不到表
default-database-strategy:
inline:
sharding-column: id
algorithm-expression: db$->{id % 3}
broadcast-tables: pub_table #公共表配置,可以多个
props:
sql:
show: true #输出日志
数据脱敏
spring:
application:
name: shardingJdbcDemo
#shardingsphere相关配置
shardingsphere:
datasource:
names: db #库名,只是名字
db: #数据源
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT
username: root
password: 123
sharding: #指定表的分片规则
# 数据脱敏规则配置---start
encrypt-rule:
encryptors:
encryptor_MD5:
type: MD5
props:
md5.key.value: 123456
encryptor_aes:
# 加密、解密器的名字,内置的为MD5,aes.
# 可以自定义,实现
# org.apache.shardingsphere.encrypt.strategy.spi.Encryptor
# 或者
# org.apache.shardingsphere.encrypt.strategy.spi.QueryAssistedEncryptor
# 这两个接口即可
type: aes
props:
aes.key.value: 123456
tables:
# tables
my_table:
columns:
# 逻辑列,就是写SQL里面的列,因为实体类的名字和数据库的加密列一致,所以这里都是name
source:
# 原文列
# plainColumn: password
# 密文列,用来存储密文数据
cipherColumn: source
# 加密器名字
encryptor: encryptor_MD5
# password1:
# 原文列
# plainColumn: password1
# 密文列,用来存储密文数据
# cipherColumn: aes_password
# 加密器名字
# encryptor: encryptor_aes
# 数据脱敏规则配置---end
broadcast-tables: pub_table #公共表
tables: # 看到table "s" ,就知道可配置多个
my_table: # 指定表名,此名必须和model中 @TableName(value = "my_table") 一致
actualDataNodes: db.my_table_$->{0..1} #创建了两个表,下标0和1,所以此处配置你懂的
tableStrategy:
inline: # 指定表的分片策略
shardingColumn: id #参与分片运算的列名
algorithmExpression: my_table_$->{id % 2} #分片算法
keyGenerator: # 指定表的主键生成策略
type: SNOWFLAKE #SNOWFLAKE
column: id #指定主键
props:
sql:
show: true #输出日志
query.with.cipher.column: true #是否使用密文列查询
分布式事务
分库分表后,因为不是一个库了,所以一旦报错,数据无法回滚
Yml
使用上方 application-sharding_db_table_masters_slaves.yml
pom
<!-- 使用 XA 事务时,需要引入此模块 -->
<dependency>
<groupId>io.shardingsphere</groupId>
<artifactId>sharding-transaction-spring-boot-starter</artifactId>
<version>3.1.0</version>
<type>pom</type>
</dependency>
Java
新增
package com.rollback.shardingjdbcdemo.demo.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class TransactionConfiguration {
@Bean
public PlatformTransactionManager txManager(final DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
修改save方法
saveByTransaction
package com.rollback.shardingjdbcdemo.demo.sharding.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.rollback.shardingjdbcdemo.demo.sharding.model.Table0;
import com.rollback.shardingjdbcdemo.demo.sharding.service.Table0Service;
import org.apache.shardingsphere.api.hint.HintManager;
import org.apache.shardingsphere.transaction.annotation.ShardingTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author koala
* @since 2021-05-31
*/
@RestController
@RequestMapping("/table0")
public class Table0Controller {
@Autowired
Table0Service table0Service;
@GetMapping("save")
public void save(int tag){
table0Service.save(new Table0(){{setSource("source"+tag);setRemark("remark"+tag);}});
}
@Transactional
@ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE
@GetMapping("saveByTransaction")
public void saveByTransaction(int tag){
for (int i = 0; i < 10; i++) {
if(i==5){
List list=null;
list.add("error");
}
table0Service.save(new Table0(){{setSource("source"+tag);setRemark("remark"+tag);}});
}
}
@GetMapping("select")
public PageInfo select(int pageNum,int pageSize,Long id){
// HintManager hintManager = HintManager.getInstance();
// hintManager.setMasterRouteOnly();
PageHelper.startPage(pageNum,pageSize);
QueryWrapper<Table0> queryWrapper=new QueryWrapper();
if(!StringUtils.isEmpty(id)) {
queryWrapper.eq("id", id);
}
queryWrapper.eq("source","source1");
List<Table0> list = table0Service.list(queryWrapper);
PageInfo pageInfo = new PageInfo<>(list);
return pageInfo;
}
@GetMapping("select1")
public List<Table0> select1(int pageNum,int pageSize,Long id){
QueryWrapper<Table0> queryWrapper=new QueryWrapper();
if(!StringUtils.isEmpty(id)) {
queryWrapper.eq("id", id);
}
//queryWrapper.eq("source","source1");
List<Table0> list = table0Service.list(queryWrapper);
return list;
}
}
验证步骤:
-
清空数据库
-
执行saveByTransaction
-
报错后观察数据库,一条都没有
-
去掉
@Transactional
@ShardingTransactionType(TransactionType.XA) // 支持TransactionType.LOCAL, TransactionType.XA, TransactionType.BASE后,会出现保存部分数据的情况
代码下载
代码见sharding包下,请求入口见 Table0Controller
以上是关于分库分表ShardingJDBC最佳实践的主要内容,如果未能解决你的问题,请参考以下文章