文章目录
概念
[参考链接:]https://share.goodnotes.com/s/odGNwaLAmfMJBoGARbTashWWW111
1、分库分表为了什么?
分库分表就是为了解决由于数据量过大而导致数据库性能(IO瓶颈,CPU瓶颈)降低的问题,将原来独立的数据库拆分成若干数据库组成 ,将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的。
2、数据库的分区、垂直分库、垂直分表、水平分库、水平分表各自解决那些问题?
分区表
分区表的分区方式有range、list、hash、key四种方式,但常用的是range、list方式
分区表可以单独对分区数据进行操作,在特定的场景下,方便对数据的老化和查询
分区表可以提高单表的存储,并且数据还可以分布在不同的物理设备上
垂直分库
解决业务层面的耦合,业务清晰
能对不同业务的数据进行分级管理、维护、监控、扩展等
高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈
垂直分表(将一张表拆成多张表)
为了避免IO争抢并减少锁表的几率
充分发挥热数据的操作效率
可以把不常用的字段单独放在一张表,如一些详细信息以及text,blob等大字段
水平分库
解决了单库大数据,高并发的性能瓶颈
提高了系统的稳定性及可用性
水平分表
优化单一表数据量过大而产生的性能问题
避免IO争抢并减少锁表的几率
3、分库分表的策略一般在什么情况下使用,使用哪种?
首先一般来说,在系统设计阶段就应该根据业务耦合松紧来确定垂直分库、垂直分表的方案。
当数据量随着业务凉的提升不断增大,但访问压力还不是特别大的情况下,我们首先考虑缓存、读写分离、索引等技术方案。
当数据量增长到特别大且持续增长时,即将或者已经出现性能瓶颈时,再考虑水平分库水平分表的方案。
4、水平分库表如何解决扩容、热点问题?
最好的方式莫过于设计前期对数据量的正确预估和业务场景的判断,尽量避免后期出现热点问题和扩容问题。
对于通过hash取模方案,没有热点问题,但会出现扩容问题,解决方案有:
停服迁移
升级从库
双写迁移
对于通过range方案,无需迁移数据,但肯能出现热点问题。所以在实际业务中,我们可以通过range+hash的配合使用来达到即支持扩容又尽可能避免热点。
5、分库分表如何解决跨库事务?
2PC两阶段提交协议
TCC事务补偿机制
最终一致性方案
最大努力通知型
业务搭建:
前景介绍
框架结构生成,前期主要采用mybatis-puls框架对数据库中的表进行操作,下面是yml的配置连接,此处省去代码展示。
由于业务量上涨,导致某个表(或者几个表)数据大,先对个表进行分库分表处理,且不影响原有的表
未分库分表的yml配置:
server:
port: 9004
spring:
application:
name: cloud-shardingSphere
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
# mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
sharding-jdbc
官网::https://shardingsphere.apache.org/index_zh.html
依赖:
<!-- sharding-jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!-- mybatis-plus version 3.3.1 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
概念
- 水平分表 (多个表结构相同的表)
- 垂直分表 (对一张表进行业务拆分)
水平分表
水平分表核心配置:
sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称,如何写的名称和库里面表名不对应的话,,例如:下图节点的名称是voucherOder,这样就无法将实体类映射了改节点,需要在实体类上加@TableName(“voucherOder”),
根据日志可知 原本的sql 语句是INSERT INTO voucher_order, 经过ShardingSphere-SQL 的捕获,将sql改为INSERT INTO tb_voucher_order_2()。分析可知:
actual-data-nodes: dsKaTeX parse error: Expected group after '_' at position 26: …b_voucher_order_̲->{1…2} ,这里需要将库名和表名写正常,ShardingSphere就是从这里获知库名和表名的
: Logic SQL: INSERT INTO voucher_order ( id,
user_id,
voucher_id,
status ) VALUES ( ?,
?,
?,
? )
2023-03-04 17:51:55.587 INFO 28364 --- [nio-9004-exec-1] ShardingSphere-SQL : SQLStatement: InsertStatement(super=DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[Table(name=voucher_order, alias=Optional.absent())]), routeConditions=Conditions(orCondition=OrCondition(andConditions=[AndCondition(conditions=[Condition(column=Column(name=id, tableName=voucher_order), operator=EQUAL, compareOperator=null, positionValueMap={}, positionIndexMap={0=0})])])), encryptConditions=Conditions(orCondition=OrCondition(andConditions=[])), sqlTokens=[TableToken(tableName=voucher_order, quoteCharacter=NONE, schemaNameLength=0), SQLToken(startIndex=27)], parametersIndex=4, logicSQL=INSERT INTO voucher_order ( id,
user_id,
voucher_id,
status ) VALUES ( ?,
?,
?,
? )), deleteStatement=false, updateTableAlias={}, updateColumnValues={}, whereStartIndex=0, whereStopIndex=0, whereParameterStartIndex=0, whereParameterEndIndex=0), columnNames=[id, user_id, voucher_id, status], values=[InsertValue(columnValues=[org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@6d92e0e1, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@58077c0f, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@2b9a1abd, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@eca6d32])])
2023-03-04 17:51:55.587 INFO 28364 --- [nio-9004-exec-1] ShardingSphere-SQL : Actual SQL: ds2 ::: INSERT INTO tb_voucher_order_2 (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631955620869337089, 1, 1, true]
yml配置
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: tb_voucher_order_1 , tb_voucher_order_2
# 这个配置是告诉sharding有多少个表
actual-data-nodes: ds1.tb_voucher_order_$->{1..2}
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order_$->{id % 2 +1} # 根据id的奇偶行进行选择表
main:
allow-bean-definition-overriding: true # mybatis 相关配置
验证:
执行原有业务,正常输出,配置了shardingsphere 的数据源,依旧是原有的数据源,至于分表的处理,sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称,该节点名称就是你所有需要分表的表名
分表功能验证
yml中配置了actual-data-nodes: ds1.tb_voucher_order_$->{1…2},
所以需要创建两个这样的表(ps:自己不知道创建表,必需手动创),
控制类:
这里没有设置id,因为yml中配置了id生成策略,这里不要在生成id了
//使用分库分表的--数据源来自shadring-jdbc 进行单库分表
@GetMapping("/queryByShadringTables")
public Result queryByShadringTables(){
TbVoucherOrder tbVoucherOrder = new TbVoucherOrder();
tbVoucherOrder.setUserId(1L);
tbVoucherOrder.setVoucherId(1L);
tbVoucherOrder.setStatus(true);
tbVoucherOrderService.save(tbVoucherOrder);
return Result.success("ture");
}
日志:
2023-03-04 16:20:32.605 INFO 38264 --- [nio-9004-exec-3] ShardingSphere-SQL : Rule Type: sharding
2023-03-04 16:20:32.605 INFO 38264 --- [nio-9004-exec-3] ShardingSphere-SQL : Logic SQL: INSERT INTO tb_voucher_order ( id,
user_id,
voucher_id,
status ) VALUES ( ?,
?,
?,
? )
2023-03-04 16:20:32.605 INFO 38264 --- [nio-9004-exec-3] ShardingSphere-SQL : SQLStatement: InsertStatement(super=DMLStatement(super=AbstractSQLStatement(type=DML, tables=Tables(tables=[Table(name=tb_voucher_order, alias=Optional.absent())]), routeConditions=Conditions(orCondition=OrCondition(andConditions=[AndCondition(conditions=[Condition(column=Column(name=id, tableName=tb_voucher_order), operator=EQUAL, compareOperator=null, positionValueMap={}, positionIndexMap={0=0})])])), encryptConditions=Conditions(orCondition=OrCondition(andConditions=[])), sqlTokens=[TableToken(tableName=tb_voucher_order, quoteCharacter=NONE, schemaNameLength=0), SQLToken(startIndex=30)], parametersIndex=4, logicSQL=INSERT INTO tb_voucher_order ( id,
user_id,
voucher_id,
status ) VALUES ( ?,
?,
?,
? )), deleteStatement=false, updateTableAlias={}, updateColumnValues={}, whereStartIndex=0, whereStopIndex=0, whereParameterStartIndex=0, whereParameterEndIndex=0), columnNames=[id, user_id, voucher_id, status], values=[InsertValue(columnValues=[org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@72ce0cad, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@3e4da476, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@3f64d109, org.apache.shardingsphere.core.parse.old.parser.expression.SQLPlaceholderExpression@51ad22ee])])
2023-03-04 16:20:32.605 INFO 38264 --- [nio-9004-exec-3] ShardingSphere-SQL : Actual SQL: ds1 ::: INSERT INTO tb_voucher_order_1 (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631932626260357122, 1, 1, true]
问题和疑问:
- 出现爆红,不影响执行
水平分库
水平分库的核心配置
-
names: ds1,ds2 # 两个库源
-
-# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达式
1.2 两点是水平分表和水平分库的区别,一个是database-strategy,一个是table-strategy:
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达式
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
main:
allow-bean-definition-overriding: true # mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
控制类:
这里没有设置id,因为yml中配置了id生成策略,这里不要在生成id了
//使用分库分表的--数据源来自shadring-jdbc 进行单库分表
@GetMapping("/queryByShadringTables")
public Result queryByShadringTables(){
TbVoucherOrder tbVoucherOrder = new TbVoucherOrder();
tbVoucherOrder.setUserId(1L);
tbVoucherOrder.setVoucherId(1L);
tbVoucherOrder.setStatus(true);
tbVoucherOrderService.save(tbVoucherOrder);
return Result.success("ture");
}
日志:
Actual SQL: ds1 ::: INSERT INTO tb_voucher_order
Actual SQL: ds2 ::: INSERT INTO tb_voucher_order
2023-03-04 17:10:12.645 INFO 3652 --- [nio-9004-exec-2] ShardingSphere-SQL : Actual SQL: ds1 ::: INSERT INTO tb_voucher_order (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631945125458231298, 1, 1, true]
2023-03-04 17:10:17.445 INFO 3652 --- [nio-9004-exec-4] ShardingSphere-SQL : Actual SQL: ds2 ::: INSERT INTO tb_voucher_order (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631945145595084801, 1, 1, true]
水平分表+水平分库
核心配置:
这个配置是告诉sharding有多少个表
actual-data-nodes: dsKaTeX parse error: Expected group after '_' at position 26: …b_voucher_order_̲->{1…2}
dsKaTeX parse error: Expected group after '_' at position 32: …b_voucher_order_̲->{1…2} 两个表
yaml
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达式
# 这个配置是告诉sharding有多少个表
actual-data-nodes: ds$->{1..2}.tb_voucher_order_$->{1..2}
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order_$->{id % 2 +1} # 根据id的奇偶行进行选择表
main:
allow-bean-definition-overriding: true # mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
日志:
2023-03-04 17:33:03.883 INFO 22764 --- [nio-9004-exec-3] ShardingSphere-SQL : Actual SQL: ds1 ::: INSERT INTO tb_voucher_order_1 (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631950876843118594, 1, 1, true]
2023-03-04 17:33:08.055 INFO 22764 --- [nio-9004-exec-6] ShardingSphere-SQL : Actual SQL: ds2 ::: INSERT INTO tb_voucher_order_2 (id, user_id, voucher_id, status) VALUES (?, ?, ?, ?) ::: [1631950894350143489, 1, 1, true]
补充:
- 正常业务的控制类:,经过测试输出正常,且默认数据源是ds1,就是第一个配置的数据源
//使用正常的业务库源
@GetMapping("/query")
public Result query(){
List<TbVoucher> list=tbVoucherService.list();
return Result.success(list);
}
- 由于上述业务使用了默认数据源,由于可以提出,如何使用数据源2去处理一些业务呢?
垂直分库
垂直分表核心配置
- 这里设置了tb_voucher_order在根据id选择在库1或者库2中的tb_voucher_order。
tb_voucher则只有在库1(ds1) - 如何tb_voucher_order也只设置一个库源的话,这个将相当于mybaits-puls设置两个库源,在写业务时指定库源即可
yml
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法 虽然垂直分库没有必要但是还要配置一下分表规则,将表和id写作死
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order
# 垂直分库、分表,其实就是写死了
tb_voucher:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds2
main:
allow-bean-definition-overriding: true # mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
控制类:
//使用正常的业务库源
@GetMapping("/query")
public Result query(){
List<TbVoucher> list=tbVoucherService.list();
return Result.success(list);
}
//使用分库分表的--数据源来自shadring-jdbc 进行单库分表
@GetMapping("/queryByShadringTables")
public Result queryByShadringTables(){
TbVoucherOrder tbVoucherOrder = new TbVoucherOrder();
tbVoucherOrder.setUserId(1L);
tbVoucherOrder.setVoucherId(1L);
tbVoucherOrder.setStatus(true);
tbVoucherOrderService.save(tbVoucherOrder);
return Result.success("ture");
}
结果:
http://localhost:9004/sharding/query请求结果如下:
{
"resultCode": {
"code": 200,
"codeMeaning": "请求成功"
},
"message": "操作成功",
"data": [
{
"id": 1,
"shopId": 1,
"title": "4",
"subTitle": "5",
"rules": "5",
"payValue": 5,
"actualValue": 5,
"type": true,
"status": true,
"createTime": "2023-03-04T06:43:11.000+0000",
"updateTime": "2023-03-04T06:43:14.000+0000"
},
{
"id": 2,
"shopId": 2,
"title": "2",
"subTitle": null,
"rules": null,
"payValue": null,
"actualValue": null,
"type": null,
"status": null,
"createTime": null,
"updateTime": null
}
]
}
日志:
2023-03-04 18:18:02.340 INFO 15012 --- [nio-9004-exec-3] ShardingSphere-SQL : Logic SQL: SELECT id,shop_id,title,sub_title,rules,pay_value,actual_value,type,status,create_time,update_time FROM tb_voucher
2023-03-04 18:18:02.341 INFO 15012 --- [nio-9004-exec-3] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,shop_id,title,sub_title,rules,pay_value,actual_value,type,status,create_time,update_time FROM tb_voucher
2023-03-04 18:18:02.341 INFO 15012 --- [nio-9004-exec-3] ShardingSphere-SQL : Actual SQL: ds2 ::: SELECT id,shop_id,title,sub_title,rules,pay_value,actual_value,type,status,create_time,update_time FROM tb_voucher
分析:
根据结果和日志分析:请求http://localhost:9004/sharding/query,ShardingSphere请求查询两个库中的tb_voucher,并且将结果合并返回.
- 判断是不是由于tb_voucher默认了数据源1,然后我又配置了数据源2 ,导致了合并结果,将tb_voucher也改成ds1,结果还是查询两个数据源
- 然后将tb_voucher_order 和tb_voucher节点的algorithm-expression都改成了ds1,但是他还是查了两个库
-
配置tb_voucher表所在的数据节点 actual-data-nodes: ds1.tb_voucher,这样查询就正常了。
结论:algorithm-expression是配置新增操作时的主键策略,由algorithm-expression判断新增数据入那个表中,actual-data-nodes:就是查询时的节点,不配置单一数据源的的话他就查两个数据源
最终yml:
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达
# algorithm-expression: ds1 # 表达式,id需要和上面的一致,groovy表达
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法 虽然垂直分库没有必要但是还要配置一下分表规则,将表和id写作死
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order
# 垂直分库、分表,其实就是写死了
tb_voucher:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds1
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds1.tb_voucher
main:
allow-bean-definition-overriding: true # mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Sharding-JDBC对不需要分库分表的普通表如何配置数据源
如何不指定默认数据源的话,结果会一会这个数据源,一会其他的,
指定不需要分表的普通表使用的数据源
default-data-source-name: ds1
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
# 指定不需要分表的普通表使用的数据源
default-data-source-name: ds1
# 默认数据源策略
# default-database-strategy:
# inline:
# algorithm-expression: subdatabase$->{userId % 2}
# sharding-column: userId
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达式
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
main:
allow-bean-definition-overriding: true # mybatis 相关配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
动态数据源
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达
# algorithm-expression: ds1 # 表达式,id需要和上面的一致,groovy表达
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法 虽然垂直分库没有必要但是还要配置一下分表规则,将表和id写作死
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order
# 垂直分库、分表,其实就是写死了
tb_voucher:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds1
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds1.tb_voucher
main:
allow-bean-definition-overriding: true # mybatis 相关配置
# 动态数据源配置
# 多数据源配置
datasource:
dynamic:
primary: mydb1
datasource:
mydb1:
url: jdbc:mysql://localhost:3306/mydb1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package org.example.common.conf;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.apache.shardingsphere.shardingjdbc.jdbc.adapter.AbstractDataSourceAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
/**
* <p> @Title DataSourceConfig
* <p> @Description 动态数据源配置(切换为sharding-jdbc数据源 => @DS(DataSourceConfig.SHARDING_DATA_SOURCE_NAME))
*
* @author ACGkaka
* @date 2022/12/21 16:01
*/
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfig {
/**
* 分表数据源名称
*/
public static final String SHARDING_DATA_SOURCE_NAME = "sharding-data-source";
/**
* 动态数据源配置项
*/
@Autowired
private DynamicDataSourceProperties properties;
/**
* sharding-jdbc有四种数据源,需要根据业务注入不同的数据源
* <p>
* 1.未使用分片, 脱敏的名称(默认): shardingDataSource;
* 2.主从数据源: masterSlaveDataSource;
* 3.脱敏数据源:encryptDataSource;
* 4.影子数据源:shadowDataSource
*/
@Lazy
@Resource(name = "shardingDataSource")
private AbstractDataSourceAdapter shardingDataSource;
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
return new AbstractDataSourceProvider() {
@Override
public Map<String, DataSource> loadDataSources() {
Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);
// 将 shardingjdbc 管理的数据源也交给动态数据源管理
dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource);
return dataSourceMap;
}
};
}
/**
* 将动态数据源设置为首选的
* 当spring存在多个数据源时, 自动注入的是首选的对象
* 设置为主要的数据源之后,就可以支持sharding-jdbc原生的配置方式了
*/
@Primary
@Bean
public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setProvider(dynamicDataSourceProvider);
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
}
import com.baomidou.dynamic.datasource.annotation.DS;
import com.demo.module.config.DataSourceConfig;
import com.demo.module.entity.TUser;
import com.demo.module.mapper.TUserMapper;
import com.demo.module.service.TUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author ACGkaka
* @since 2021-04-25
*/
@Service
@DS(DataSourceConfig.SHARDING_DATA_SOURCE_NAME)
public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements TUserService {
@DS("mydb1")
@Override
public List<TUser> listFromDB1() {
// 查询 全部用户(mydb1数据库)
return this.list();
}
}
没有起作用:
shardingsphere+dynamic-datasource配置多数据源
依赖:
分页
Sharding支持分页查询的数据库?
(1)完全支持MySQL、PostgreSQL和Oracle的分页查询;
(2)SQLServer由于分页查询较为复杂,仅部分支持。
Sharding-JDBC分页修正
从多个数据库获取分页数据与单数据库的场景是不同的。 假设每10条数据为一页,取第2页数据。在分片环境下获取LIMIT 10, 10,归并之后再根据排序条件取出前10条数据是不正确的。 举例说明,若SQL为:
SELECT score FROM t_score ORDER BY score DESC LIMIT 1, 2;
下图展示了不进行SQL的改写的分页执行结果:
通过图中所示,想要取得两个表中共同的按照分数排序的第2条和第3条数据,应该是95和90。 由于执行的SQL只能从每个表中获取第2条和第3条数据,即从t_score_0表中获取的是90和80;从t_score_0表中获取的是85和75。 因此进行结果归并时,只能从获取的90,80,85和75之中进行归并,那么结果归并无论怎么实现,都不可能获得正确的结果。
正确的做法是将分页条件改写为LIMIT 0, 3,取出所有前两页数据,再结合排序条件计算出正确的数据。 下图展示了进行SQL改写之后的分页执行结果。
全量查询时
控制类:
//使用分库分表的--数据源来自shadring-jdbc 进行单库分表 全量查询
@GetMapping("/queryByShadringTables")
public Result<List<TbVoucherOrder>> queryByShadringTables(){
List<TbVoucherOrder> list= tbVoucherOrderService.list();
return Result.success(list);
}
日志:
2023-03-05 10:34:34.474 INFO 36868 --- [nio-9004-exec-4] ShardingSphere-SQL : Rule Type: sharding
2023-03-05 10:34:34.474 INFO 36868 --- [nio-9004-exec-4] ShardingSphere-SQL : Logic SQL: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
2023-03-05 10:34:34.475 INFO 36868 --- [nio-9004-exec-4] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
2023-03-05 10:34:34.475 INFO 36868 --- [nio-9004-exec-4] ShardingSphere-SQL : Actual SQL: ds2 ::: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
结论:
根据日志分析,该结果是同时查了两个库源表(ds2 ::: SELECT /ds1 ::: SELECT)的合并结果
分页查询:
yml
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
# 指定不需要分表的普通表使用的数据源
default-data-source-name: ds1
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达
# algorithm-expression: ds1 # 表达式,id需要和上面的一致,groovy表达
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法 虽然垂直分库没有必要但是还要配置一下分表规则,将表和id写作死
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order
# 垂直分库、分表,其实就是写死了
tb_voucher:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds1
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds1.tb_voucher
# # 配置数据源的读写分离,但是数据库一定要做主从复制
main:
allow-bean-definition-overriding: true # mybatis 相关配置
# 动态数据源配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置config
package org.example.common.conf;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:
**/
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
代码
//controller
//使用分库分表的--数据源来自shadring-jdbc 进行单库分表 分页查询
@GetMapping("/queryPageByShadringTables")
public Result<Page> queryPageByShadringTables(){
Page<TbVoucherOrder> page = new Page<>(1,2);
QueryWrapper<TbVoucherOrder> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
// Page<TbVoucherOrder> tbVoucherOrderPage = tbVoucherOrderService.queryPage(page, queryWrapper);
Page<TbVoucherOrder> tbVoucherOrderPage = tbVoucherOrderService.page(page, queryWrapper);
return Result.success(tbVoucherOrderPage);
}
//service
public interface TbVoucherOrderService extends IService<TbVoucherOrder> {
public Page<TbVoucherOrder> queryPage(Page<TbVoucherOrder> page, Wrapper<TbVoucherOrder> queryWrapper);
}
//impl
@Service
public class TbVoucherOrderServiceImpl extends ServiceImpl<TbVoucherOrderMapper, TbVoucherOrder> implements TbVoucherOrderService {
@Resource
private TbVoucherOrderMapper tbVoucherOrderMapper;
@Override
public Page<TbVoucherOrder> queryPage(Page<TbVoucherOrder> page, Wrapper<TbVoucherOrder> queryWrapper) {
return tbVoucherOrderMapper.selectPage(page, queryWrapper);
}
}
日志:
2023-03-05 11:23:07.116 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Logic SQL: SELECT COUNT(1) FROM tb_voucher_order
2023-03-05 11:23:07.117 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT COUNT(1) FROM tb_voucher_order
2023-03-05 11:23:07.117 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Actual SQL: ds2 ::: SELECT COUNT(1) FROM tb_voucher_order
2023-03-05 11:23:07.504 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Rule Type: sharding
2023-03-05 11:23:07.504 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Logic SQL: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
ORDER BY id DESC LIMIT ?,?
2023-03-05 11:23:07.505 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Actual SQL: ds1 ::: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
ORDER BY id DESC LIMIT ?,? ::: [0, 2]
2023-03-05 11:23:07.505 INFO 37292 --- [nio-9004-exec-1] ShardingSphere-SQL : Actual SQL: ds2 ::: SELECT id,user_id,voucher_id,pay_type,status,create_time,pay_time,user_time,refund_time,update_time FROM tb_voucher_order
ORDER BY id DESC LIMIT ?,? ::: [0, 2]
结果:
{
"resultCode": {
"code": 200,
"codeMeaning": "请求成功"
},
"message": "操作成功",
"data": {
"records": [
{
"id": 1632223348515594241,
"userId": 1,
"voucherId": 1,
"payType": null,
"status": true,
"createTime": "2023-03-05T03:36:13.065+0000",
"payTime": null,
"userTime": null,
"refundTime": null,
"updateTime": "2023-03-05T03:36:11.000+0000"
},
{
"id": 1632223342593236993,
"userId": 1,
"voucherId": 1,
"payType": null,
"status": true,
"createTime": "2023-03-05T03:36:10.631+0000",
"payTime": null,
"userTime": null,
"refundTime": null,
"updateTime": "2023-03-05T03:36:09.000+0000"
}
],
"total": 17,
"size": 2,
"current": 2,
"orders": [],
"hitCount": false,
"searchCount": true,
"pages": 9
}
}
分析:
选择的时第二页的数据,且按照id降序排列,如果是全量表的话就是取第三条和第四条数据;
根据上图可知:1632223342593236993,1632223348515594241
而Sharding是取两个表的前4个数据,合并,然后在取前两个条
返回结果是:1632223342593236993,1632223348515594241
排序结果正确
总结:
当查询limit 1,2 时,Sharding对两个分库的表处理时LIMIT ?,? ::: [0, 2]
当查询limit 2,2 时,Sharding对两个分库的表处理时LIMIT ?,? ::: [0, 4]
当查询limit 7,2 时,Sharding对两个分库的表处理时LIMIT ?,? ::: [0, 14]
如果有一千万数据时,按月分12个表时,查询最后一页时;
全表:select count(1 ) from 一千万
select * from 一千万 limit 一千万:10
分表:select count(1 ) from 一百万 *
select * from 一百万 limit 一百万:10
然后Sharding 进行整合取最后一页
Sharding-JDBC的优化
(1)采用流式处理 + 归并排序的方式来避免内存的过量占用。由于SQL改写不可避免的占用了额外的带宽,但并不会导致内存暴涨。 与直觉不同,大多数人认为Sharding-JDBC会将1,000,010 * 2记录全部加载至内存,进而占用大量内存而导致内存溢出。 但由于每个结果集的记录是有序的,因此Sharding-JDBC每次仅获取各个分片的当前结果集记录,驻留在内存中的记录仅为当前路由到的分片的结果集的当前游标指向而已。 对于本身即有序的待排序对象,归并排序的时间复杂度仅为O(n),性能损耗很小。
(2)Sharding-JDBC对仅落至单分片的查询进行进一步优化。 落至单分片查询的请求并不需要改写SQL也可以保证记录的正确性,因此在此种情况下,Sharding-JDBC并未进行SQL改写,从而达到节省带宽的目的。
分页方案优化
由于LIMIT并不能通过索引查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案:(不支持跳页)
或通过记录上次查询结果的最后一条记录的ID进行下一页的查询:
解决方案:https://blog.csdn.net/weixin_57907028/article/details/127053254
方式二:这里我们主要基于java config的方式来集成到springboot
读写分离
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
Sharding-JDBC提供一主多从的读写分离配置,可独立使用,也可配合分库分表使用,同一线程且同一数据库连接 内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性。Sharding-JDBC不提供主从数据库的数据同步功能,需要采用其他机制支持。
在这里插入图片描述
yml:
server:
port: 9004
#spring:
# application:
# name: cloud-shardingSphere
# datasource:
# type: com.alibaba.druid.pool.DruidDataSource
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&serverTimezone=CTT&characterEncoding=UTF-8&useSSL=false
# username: root
# password: 123456
#spring:
# config:
# use-legacy-processing: true
# shardingsphere:
# datasource:
# ds1:
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.alibaba.druid.pool.DruidDataSource
# url: jdbc:mysql://127.0.0.1:3306/sharding-jdbc2?serverTimezone=UTC
# username: root
# password: 123456
# names: ds1
# props:
# sql:
# show: true
# sharding:
# tables:
# bu_order:
# actual-data-nodes: ds1.bu_order_$->{0..1}
# key-generator:
# column: order_id
# type: SNOWFLAKE
# table-strategy:
# inline:
# algorithm-expression: bu_order_${user_id%2}
# sharding-column: user_id
spring:
# 相关的配置
shardingsphere:
props:
sql:
show: true # 是否展示sql
datasource: # 数据源配置
names: ds1,ds2 # 两个库源
ds1:
type: com.zaxxer.hikari.HikariDataSource # 数据库连接池类型:HikariDataSource 是springboot自带的数据库连接池
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: 123456
ds2:
# 数据库连接池
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动
driver-class-name: com.mysql.jdbc.Driver
# 数据库链接
jdbcUrl: jdbc:mysql://localhost:13306/mybatis?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
# 数据库用户名
username: root
# 数据库密码
password: 123
# 分库的策略
sharding:
# 指定不需要分表的普通表使用的数据源
default-data-source-name: ds1
tables:
# 这个地方注意: sharding-jdbc会根据名称去找本节点,所以写sql的时候,要写此节点的名称
tb_voucher_order:
# 表达式, 健康节点: 根据上一个节点找到此值, {1..2}为groovy语言,$会替换成{1..2}的一个值,数据库表是: ds1.tb_voucher_order , ds2.tb_voucher_order
# 配置数据库的分库策略
database-strategy:
# 行表达式模式
inline:
# 选择需要分库的字段,根据那个字段进行区分
sharding-column: id
algorithm-expression: ds$->{id % 2 + 1} # 表达式,id需要和上面的一致,groovy表达
# algorithm-expression: ds1 # 表达式,id需要和上面的一致,groovy表达
actual-data-nodes: ds$->{1..2}.tb_voucher_order
# 主键生成策略
key-generator:
# 对应的数据库表的主键
column: id
# 生成方式, 雪花模式
type: SNOWFLAKE
# 配置其分片策略和分片算法 虽然垂直分库没有必要但是还要配置一下分表规则,将表和id写作死
table-strategy:
# 行表达式
inline:
# 配置sharding的计算列
sharding-column: id
# 配置sharding的表达式,对应的n_id必须和sharding-column的值对应,否则报错
algorithm-expression: tb_voucher_order
# 垂直分库、分表,其实就是写死了
tb_voucher:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds1
#配置tb_voucher表所在的数据节点
actual-data-nodes: ds1.tb_voucher
# # 配置数据源的读写分离,但是数据库一定要做主从复制
masterslave:
# 配置主从名称,可以任意取名字
name: ms
# 配置主库master,负责数据的写入
master-data-source-name: ds1
# 配置从库slave节点
slave-data-source-names: ds2
# 配置slave节点的负载均衡均衡策略,采用轮询机制、随机策略
load-balance-algorithm-type: round_robin
main:
allow-bean-definition-overriding: true # mybatis 相关配置
# 动态数据源配置
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: org.example.entity
configuration:
# 是否打印 sql 语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
日志:
查询
2023-03-04 21:50:17.877 INFO 40512 --- [(5)-172.21.32.1] ShardingSphere-SQL : Rule Type: master-slave
2023-03-04 21:50:17.877 INFO 40512 --- [(5)-172.21.32.1] ShardingSphere-SQL : SQL: /* ping */ SELECT 1 ::: DataSources: ds2
2023-03-04 21:51:29.384 INFO 40512 --- [nio-9004-exec-1] ShardingSphere-SQL : Rule Type: master-slave
2023-03-04 21:51:29.384 INFO 40512 --- [nio-9004-exec-1] ShardingSphere-SQL : SQL: SELECT id,username,password,gender,addr,deleted,job_name FROM tb_user ::: DataSources: ds2
//新增
2023-03-04 21:54:02.399 INFO 40512 --- [nio-9004-exec-6] ShardingSphere-SQL : Rule Type: master-slave
2023-03-04 21:54:02.399 INFO 40512 --- [nio-9004-exec-6] ShardingSphere-SQL : SQL: INSERT INTO tb_voucher_order ( id,
user_id,
voucher_id,
status ) VALUES ( ?,
?,
?,
? ) ::: DataSources: ds1
结论:读写分离正常,但是这样分库就没有用了,
读写分离能不能指定表?