重庆网上商城网站建设,长沙网络营销哪家好,潍坊个人做网站的公司,v2ex wordpress主题前言
学习视频#xff1a;深入Sharding-JDBC分库分表从入门到精通【黑马程序员】本内容仅用于个人学习笔记#xff0c;如有侵扰#xff0c;联系删除
1、概述
1.1、分库分表是什么
小明是一家初创电商平台的开发人员#xff0c;他负责卖家模块的功能开发#xff0c;其中…前言
学习视频深入Sharding-JDBC分库分表从入门到精通【黑马程序员】本内容仅用于个人学习笔记如有侵扰联系删除
1、概述
1.1、分库分表是什么
小明是一家初创电商平台的开发人员他负责卖家模块的功能开发其中涉及了店铺、商品的相关业务设计如下
数据库 通过以下SQL能够获取到商品相关的店铺信息、地理区域信息
SELECT p.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [地理区域] r ON p.[产地] r.[地理区域编码]
LEFT JOIN [店铺信息] s ON p.id s.[所属店铺]
WHERE p.id ?形成类似以下列表展示 随着公司业务快速发展数据库中的数据量猛增访问性能也变慢了优化迫在眉睫。分析一下问题出现在哪儿呢 关系型数据库本身比较容易成为系统瓶颈单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后由于查询维度较多即使添加从库、优化索引做很多操作时性能仍下降严重。
方案1
通过提升服务器硬件能力来提高数据处理能力比如增加存储容量 、CPU等这种方案成本很高并且如果瓶颈在MySQL本身那么提高硬件也是有很的。
方案2
把数据分散在不同的数据库中使得单一数据库的数据量变小来缓解单一数据库的性能问题从而达到提升数据库性能的目的如下图将电商数据库拆分为若干独立的数据库并且对于大表也拆分为若干小表通过这种数据库拆分的方法来解决数据库的性能问题。 分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题将原来独立的数据库拆分成若干数据库组成将数据大表拆分成若干数据表组成使得单一数据库、单一数据表的数据量变小从而达到提升数据库性能的目的。
1.2、分库分表的方式
分库分表包括分库和分表两个部分在生产中通常包括垂直分库、水平分库、垂直分表、水平分表四种方式。
1.2.1、垂直分表
下边通过一个商品查询的案例讲解垂直分表 通常在商品列表中是不显示商品详情信息的如下图
用户在浏览商品列表时只有对某商品感兴趣时才会查看该商品的详细描述。因此商品信息中商品描述字段访问频次较低且该字段存储占用空间较大访问单个数据IO时间较长商品信息中商品名称、商品图片、商品价格等其他字段数据访问频次较高。
由于这两种数据的特性不一样因此他考虑将商品信息表拆分如下
将访问频次低的商品描述信息单独存放在一张表中访问频次较高的商品基本信息单独放在一张表中。 商品列表可采用以下sql
SELECT p.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [地理区域] r ON p.[产地] r.[地理区域编码]
LEFT JOIN [店铺信息] s ON p.id s.[所属店铺]
WHERE...ORDER BY...LIMIT...需要获取商品描述时再通过以下sql获取
SELECT *
FROM [商品描述]
WHERE [商品ID] ?小明进行的这一步优化就叫垂直分表。
垂直分表定义将一个表按照字段分成多表每个表存储其中一部分字段。
它带来的提升是
为了避免IO争抢并减少锁表的几率查看详情的用户与商品信息浏览互不影响充分发挥热门数据的操作效率商品信息的操作的高效率不会被商品描述的低效率所拖累。
一般来说某业务实体中的各个数据项的访问频次是不一样的部分数据项可能是占用存储空间比较大的BLOB或是TEXT。例如上例中的商品描述。所以当表数据量很大时可以将表按字段切开将热门字段、冷门字段分开放置在不同库中这些库可以放在不同的存储设备上避免IO争抢。垂直切分带来的性能提升主要集中在热门数据的操作效率上而且磁盘争用情况减少。
通常我们按以下原则进行垂直拆分:
把不常用的字段单独放在一张表;把textblob等大字段拆分出来放在附表中;经常组合查询的列放在一张表中;
1.2.2、垂直分库
通过垂直分表性能得到了一定程度的提升但是还没有达到要求并且磁盘空间也快不够了因为数据还是始终限制在一台服务器库内垂直分表只解决了单一表数据量过大的问题但没有将表分布到不同的服务器上因此每个表还是竞争同一个物理机的CPU、内存、网络IO、磁盘。
经过思考他把原有的SELLER_DB(卖家库)分为了PRODUCT_DB(商品库)和STORE_DB(店铺库)并把这两个库分散到不同服务器如下图 由于商品信息与商品描述业务耦合度较高因此一起被存放在PRODUCT_DB(商品库)而店铺信息相对独立因此单独被存放在STORE_DB(店铺库)。
小明进行的这一步优化就叫垂直分库。
垂直分库是指按照业务将表进行分类分布到不同的数据库上面每个库可以放在不同的服务器上它的核心理念是专库专用。
它带来的提升是
解决业务层面的耦合业务清晰能对不同业务的数据进行分级管理、维护、监控、扩展等高并发场景下垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈
垂直分库通过将表按业务分类然后分布在不同数据库并且可以将这些数据库部署在不同服务器上从而达到多个服务器共同分摊压力的效果但是依然没有解决单表数据量过大的问题。
1.2.3、水平分库
经过垂直分库后数据库性能问题得到一定程度的解决但是随着业务量的增长PRODUCT_DB(商品库)单库存储数据已经超出预估。粗略估计目前有8w店铺每个店铺平均150个不同规格的商品再算上增长那商品数量得往1500w上预估并且PRODUCT_DB(商品库)属于访问非常频繁的资源单台服务器已经无法支撑。此时该如何优化
再次分库但是从业务角度分析目前情况已经无法再次垂直分库。
尝试水平分库将店铺ID为单数的和店铺ID为双数的商品信息分别放在两个库中。 也就是说要操作某条数据先分析这条数据所属的店铺ID。如果店铺ID为双数将此操作映射至 RRODUCT_DB1(商品库1)如果店铺ID为单数将操作映射至RRODUCT_DB2(商品库2)。此操作要访问数据库名称的表达式为RRODUCT_DB[店铺ID%2 1] 。
小明进行的这一步优化就叫水平分库。
水平分库是把同一个表的数据按一定规则拆到不同的数据库中每个库可以放在不同的服务器上。
它带来的提升是
解决了单库大数据高并发的性能瓶颈。提高了系统的稳定性及可用性。
当一个应用难以再细粒度的垂直切分或切分后数据量行数巨大存在单库读写、存储性能瓶颈这时候就需要进行水平分库了经过水平切分的优化往往能解决单库存储量及性能瓶颈。但由于同一个表被分配在不同的数据库需要额外进行数据操作的路由工作因此大大提升了系统复杂度。
1.2.4、水平分表
按照水平分库的思路对他把PRODUCT_DB_X(商品库)内的表也可以进行水平拆分其目的也是为解决单表数据量大的问题如下图 与水平分库的思路类似不过这次操作的目标是表商品信息及商品描述被分成了两套表。如果商品ID为双数将此操作映射至商品信息1表如果商品ID为单数将操作映射至商品信息2表。此操作要访问表名称的表达式为商品信息[商品ID%2 1] 。
小明进行的这一步优化就叫水平分表。
水平分表是在同一个数据库内把同一个表的数据按一定规则拆到多个表中。
它带来的提升是
优化单一表数据量过大而产生的性能问题避免IO争抢并减少锁表的几率
库内的水平分表解决了单一表数据量过大的问题分出来的小表中只包含一部分数据从而使得单个表的数据量变小提高检索性能。
1.2.5、小结
本章介绍了分库分表的各种方式它们分别是垂直分表、垂直分库、水平分库和水平分表
垂直分表 可以把一个宽表的字段按访问频次、是否是大字段的原则拆分为多个表这样既能使业务清晰还能提升部分性能。拆分后尽量从业务角度避免联查否则性能方面将得不偿失。
垂直分库 可以把多个表按业务耦合松紧归类分别存放在不同的库这些库可以分布在不同服务器从而使访问压力被多服务器负载大大提升性能同时能提高整体架构的业务清晰度不同的业务库可根据自身情况定制优化方案。但是它需要解决跨库带来的所有复杂问题。
水平分库 可以把一个表的数据(按数据行)分到多个不同的库每个库只有这个表的部分数据这些库可以分布在不同服务器从而使访问压力被多服务器负载大大提升性能。它不仅需要解决跨库带来的所有复杂问题还要解决数据路由的问题(数据路由问题后边介绍)。
水平分表 可以把一个表的数据(按数据行)分到多个同一个数据库的多张表中每个表只有这个表的部分数据这样做能小幅提升性能它仅仅作为水平分库的一个补充优化。
一般来说在系统设计阶段就应该根据业务耦合松紧来确定垂直分库垂直分表方案在数据量及访问压力不是特别大的情况首先考虑缓存、读写分离、索引技术等方案。若数据量极大且持续增长再考虑水平分库水平分表方案。
1.3、分库分表带来的问题
分库分表能有效的缓解了单机和单库带来的性能瓶颈和压力突破网络IO、硬件资源、连接数的瓶颈同时也带来了一些问题。
1.3.1、事务一致性问题
由于分库分表把数据分布在不同库甚至不同服务器不可避免会带来分布式事务问题。
1.3.2、跨节点关联查询
在没有分库前我们检索商品时可以通过以下SQL对店铺信息进行关联查询
SELECT p.*,r.[地理区域名称],s.[店铺名称],s.[信誉]
FROM [商品信息] p
LEFT JOIN [地理区域] r ON p.[产地] r.[地理区域编码]
LEFT JOIN [店铺信息] s ON p.id s.[所属店铺]
WHERE...ORDER BY...LIMIT...但垂直分库后[商品信息]和[店铺信息]不在一个数据库甚至不在一台服务器无法进行关联查询。 可将原关联查询分为两次查询第一次查询的结果集中找出关联数据id然后根据id发起第二次请求得到关联数 据最后将获得到的数据进行拼装。
1.3.3、跨节点分页、排序函数
跨节点多库进行查询时limit分页、order by排序等问题就变得比较复杂了。需要先在不同的分片节点中将数据进行排序并返回然后将不同分片返回的结果集进行汇总和再次排序。
如进行水平分库后的商品库按ID倒序排序分页取第一页 以上流程是取第一页的数据性能影响不大但由于商品信息的分布在各数据库的数据可能是随机的如果是取第N页需要将所有节点前N页数据都取出来合并再进行整体的排序操作效率可想而知。所以请求页数越大系统的性能也会越差。
在使用Max、Min、Sum、Count之类的函数进行计算的时候与排序分页同理也需要先在每个分片上执行相应的函数然后将各个分片的结果集进行汇总和再次计算最终将结果返回。
1.3.4、主键避重
在分库分表环境中由于表中数据同时存在不同数据库中主键值平时使用的自增长将无用武之地某个分区数据库生成的ID无法保证全局唯一。因此需要单独设计全局主键以避免跨库主键重复问题。 1.3.5、公共表
实际的应用场景中参数表、数据字典表等都是数据量较小变动少而且属于高频联合查询的依赖表。例子中地理区域表也属于此类型。
可以将这类表在每个数据库都保存一份所有对公共表的更新操作都同时发送到所有分库执行。
由于分库分表之后数据被分散在不同的数据库、服务器。因此对数据的操作也就无法通过常规方式完成并且它还带来了一系列的问题。好在这些问题不是所有都需要我们在应用层面上解决市面上有很多中间件可供我们选择其中Sharding-JDBC使用流行度较高我们来了解一下它。
1.4、Sharding-JDBC介绍
1.4.1、Sharding-JDBC介绍
Sharding-JDBC是当当网研发的开源分布式数据库中间件从 3.0 开始Sharding-JDBC被包含在 Sharding-Sphere中之后该项目进入进入Apache孵化器4.0版本之后的版本为Apache版本。
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar计划中这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
官方地址https://shardingsphere.apache.org/document/current/cn/overview/
咱们目前只需关注Sharding-JDBC它定位为轻量级Java框架在Java的JDBC层提供的额外服务。 它使用客户端直连数据库以jar包形式提供服务无需额外部署和依赖可理解为增强版的JDBC驱动完全兼容JDBC和各种ORM框架。
Sharding-JDBC的核心功能为数据分片和读写分离通过Sharding-JDBC应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源而不用关心数据源的数量以及数据如何分布。
适用于任何基于Java的ORM框架如 Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。基于任何第三方的数据库连接池如DBCP, C3P0, BoneCP, Druid, HikariCP等。支持任意实现JDBC规范的数据库。目前支持MySQLOracleSQLServer和PostgreSQL。 上图展示了Sharding-Jdbc的工作方式使用Sharding-Jdbc前需要人工对数据库进行分库分表在应用程序中加入Sharding-Jdbc的Jar包应用程序通过Sharding-Jdbc操作分库分表后的数据库和数据表由于Sharding-Jdbc是对Jdbc驱动的增强使用Sharding-Jdbc就像使用Jdbc驱动一样在应用程序中是无需指定具体要操作的分库和分表的。
1.4.2、与jdbc性能对比
性能损耗测试服务器资源充足、并发数相同比较JDBC和Sharding-JDBC性能损耗Sharding-JDBC相对JDBC损耗不超过7%。 性能对比测试服务器资源使用到极限相同的场景JDBC与Sharding-JDBC的吞吐量相当。性能对比测试服务器资源使用到极限Sharding-JDBC采用分库分表后Sharding-JDBC吞吐量较JDBC不分表有接近2倍的提升。
2、Sharding-JDBC快速入门
2.1、需求说明
本章节使用Sharding-JDBC完成对订单表的水平分表通过快速入门程序的开发快速体验Sharding-JDBC的使用方法。
人工创建两张表t_order_1和t_order_2这两张表是订单表拆分后的表通过Sharding-Jdbc向订单表插入数据按照一定的分片规则主键为偶数的进入t_order_1另一部分数据进入t_order_2通过Sharding-Jdbc 查询数据根据 SQL语句的内容从t_order_1或t_order_2查询数据。
2.2、环境搭建
2.2.1、环境说明
操作系统Win10数据库MySQL-5.7.25JDK64位 jdk1.8.0_201应用框架spring-boot-2.7.6mybatis-plus 3.5.3.1Sharding-JDBCsharding-jdbc-spring-boot-starter-4.0.0-RC1
2.2.2、创建数据库
创建订单库order_db
CREATE DATABASE order_db CHARACTER SET utf8 COLLATE utf8_general_ci;在order_db中创建t_order_1、t_order_2表
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;2.2.3、引入maven依赖
引入 sharding-jdbc和SpringBoot整合的Jar包
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.6/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdcom.wts/groupIdartifactIdspringboot_sharding_jdbc/artifactIdversion0.0.1-SNAPSHOT/versionnamespringboot_sharding_jdbc/namedescriptionspringboot_sharding_jdbc/descriptionpropertiesjava.version1.8/java.versionproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingspring-boot.version2.7.6/spring-boot.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3.1/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.18/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.apache.shardingsphere/groupIdartifactIdsharding-jdbc-spring-boot-starter/artifactIdversion4.0.0-RC1/version/dependency/dependenciesdependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion${spring-boot.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project2.3、编写程序
2.3.1、分片规则配置
分片规则配置是sharding-jdbc进行对分库分表操作的重要依据配置内容包括数据源、主键生成策略、分片策略等。
在application.properties中配置
server.port56081
server.servlet.context‐path /sharding‐jdbc‐simple‐demo
server.servlet.encoding.enabled true
server.servlet.encoding.charset UTF-8
server.servlet.encoding.force true
spring.main.allow‐bean‐definition‐overriding true
mybatis.configuration.map‐underscore‐to‐camel‐case true# 以下是分片规则配置
# 定义数据源
spring.shardingsphere.datasource.names m1
spring.shardingsphere.datasource.m1.typecom.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m1.username root
spring.shardingsphere.datasource.m1.password root
# 指定t_order表的数据分布情况配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes m1.t_order_$-{1..2}# 指定t_order表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.column order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type SNOWFLAKE
# 指定t_order表的分片策略分片策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression t_order_$-{order_id % 2 1}
# 打开sql输出日志
spring.shardingsphere.props.sql.show true
swagger.enable true
logging.level.root info
logging.level.org.springframework.web info
logging.level.com.itheima.dbsharding debug
logging.level.druid.sql debug首先定义数据源m1并对m1进行实际的参数配置。指定t_order表的数据分布情况他分布在m1.t_order_1m1.t_order_2指定t_order表的主键生成策略为SNOWFLAKESNOWFLAKE是一种分布式自增算法保证id全局唯一定义t_order分片策略order_id为偶数的数据落在t_order_1为奇数的落在t_order_2分表策略的表达式为t_order_$-{order_id % 2 1}
2.3.2、数据操作
Mapper
Component
public interface OrderDao {/*** 新增订单** param price 订单价格* param userId 用户id* param status 订单状态* return*/Insert(insert into t_order(price, user_id, status) value(#{price}, #{userId}, #{status}))int insertOrder(Param(price) BigDecimal price, Param(userId) Long userId, Param(status) String status);/*** 根据id列表查询多个订单** param orderIds 订单id列表* return*/Select({script select * from t_order t where t.order_id in foreach collectionorderIds itemid open( separator, close) #{id} /foreach /script})ListMap selectOrderbyIds(Param(orderIds) ListLong orderIds);
}2.3.3、测试
编写单元测试
SpringBootTest
class SpringbootShardingJdbcApplicationTests {Testvoid contextLoads() {}Autowiredprivate OrderDao orderDao;Testpublic void testInsertOrder() {for (int i 0; i 10; i) {orderDao.insertOrder(new BigDecimal((i 1) * 5), 1L, WAIT_PAY);}}Testpublic void testSelectOrderByIds() {ListLong ids new ArrayList();ids.add(1138054658762211328L);ids.add(1138054658510553089L);ListMap maps orderDao.selectOrderbyIds(ids);System.out.println(maps);}
}执行testInsertOrder
通过日志可以发现order_id为奇数的被插入到t_order_2表为偶数的被插入到t_order_1表达到预期目标。 执行testSelectOrderbyIds
通过日志可以发现根据传入order_id的奇偶不同sharding-jdbc分别去不同的表检索数据达到预期目标。
2.4、流程分析
通过日志分析Sharding-JDBC在拿到用户要执行的sql之后干了哪些事儿 1解析sql获取片键值在本例中是order_id 2Sharding-JDBC通过规则配置 t_order_$-{order_id % 2 1}知道了当order_id为偶数时应该往t_order_1表插数据为奇数时往t_order_2插数据。 3于是Sharding-JDBC根据order_id的值改写sql语句改写后的SQL语句是真实所要执行的SQL语句。 4执行改写后的真实sql语句 5将所有真正执行sql的结果进行汇总合并返回。
2.5、其他集成方式
Sharding-JDBC不仅可以与spring boot良好集成它还支持其他配置方式共支持以下四种集成方式。
1)、Spring Boot Yaml 配置
定义application.yml内容如下
server:port: 56081servlet:context-path: /sharding-jdbc-demoencoding:enabled: truecharset: UTF-8force: true
spring:application:name: sharding-jdbc-demomain:allow-bean-definition-overriding: truesharding-sphere:datasource:names: m1 # 数据源名称列表m1: # 数据源具体配置type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8.0 驱动url: jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghaiusername: rootpassword: rootsharding:tables:t_order:actual-data-nodes: m1.t_order_$-{1..2}key-generator:column: order_idtype: SNOWFLAKEtable-strategy:inline:sharding-column: order_idalgorithm-expression: t_order_$-{order_id % 2 1}props:sql:show: truemybatis:configuration:map-underscore-to-camel-case: trueswagger:enable: truelogging:level:root: infoorg.springframework.web: infocom.wf.game.sdk: debugdruid.sql: debug如果使用application.yml则需要屏蔽原来的application.properties文件。
2)、Java 配置 添加配置类
Configuration
public class ShardingJdbcConfig {/*** 定义数据源** return*/MapString, DataSource createDataSourceMap() {DruidDataSource dataSource1 new DruidDataSource();dataSource1.setDriverClassName(com.mysql.jdbc.Driver);dataSource1.setUrl(jdbc:mysql://172.28.133.3:3306/order_db?useUnicodetruecharacterEncodingUTF-8useSSLtrueserverTimezoneAsia/Shanghai);dataSource1.setUsername(root);dataSource1.setPassword(root);MapString, DataSource result new HashMap();result.put(m1, dataSource1);return result;}/*** 定义主键生成策略** return*/private static KeyGeneratorConfiguration getKeyGeneratorConfiguration() {KeyGeneratorConfiguration result newKeyGeneratorConfiguration(SNOWFLAKE, order_id);return result;}/*** 定义t_order表的分片策略** return*/TableRuleConfiguration getOrderTableRuleConfiguration() {TableRuleConfiguration result new TableRuleConfiguration(t_order, m1.t_order_$-{1..2});result.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration(order_id, t_order_$-{order_id % 2 1}));result.setKeyGeneratorConfig(getKeyGeneratorConfiguration());return result;}/*** 定义sharding‐Jdbc数据源** return* throws SQLException*/BeanDataSource getShardingDataSource() throws SQLException {ShardingRuleConfiguration shardingRuleConfig new ShardingRuleConfiguration();shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());//spring.shardingsphere.props.sql.show trueProperties properties new Properties();properties.put(sql.show, true);return ShardingDataSourceFactory.createDataSource(createDataSourceMap(), shardingRuleConfig, properties);}
}由于采用了配置类所以需要屏蔽原来application.properties文件中spring.shardingsphere开头的配置信息。
server.port56081
server.servlet.context‐path /sharding‐jdbc‐simple‐demo
server.servlet.encoding.enabled true
server.servlet.encoding.charset UTF-8
server.servlet.encoding.force true
spring.main.allow‐bean‐definition‐overriding true
mybatis.configuration.map‐underscore‐to‐camel‐case true# 以下是分片规则配置
# 定义数据源
#spring.shardingsphere.datasource.names m1
#spring.shardingsphere.datasource.m1.typecom.alibaba.druid.pool.DruidDataSource
#spring.shardingsphere.datasource.m1.driver‐class‐name com.mysql.cj.jdbc.Driver
#spring.shardingsphere.datasource.m1.url jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
#spring.shardingsphere.datasource.m1.username root
#spring.shardingsphere.datasource.m1.password root
## 指定t_order表的数据分布情况配置数据节点
#spring.shardingsphere.sharding.tables.t_order.actual-data-nodes m1.t_order_$-{1..2}
#
## 指定t_order表的主键生成策略为SNOWFLAKE
#spring.shardingsphere.sharding.tables.t_order.key-generator.column order_id
#spring.shardingsphere.sharding.tables.t_order.key-generator.type SNOWFLAKE
## 指定t_order表的分片策略分片策略包括分片键和分片算法
#spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column order_id
#spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression t_order_$-{order_id % 2 1}
## 打开sql输出日志
#spring.shardingsphere.props.sql.show trueswagger.enable true
logging.level.root info
logging.level.org.springframework.web info
logging.level.com.itheima.dbsharding debug
logging.level.druid.sql debug还需要在SpringBoot启动类中屏蔽使用spring.shardingsphere配置项的类SpringBootConfiguration
package com.wts;import org.apache.shardingsphere.shardingjdbc.spring.boot.SpringBootConfiguration;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication(exclude SpringBootConfiguration.class)
public class SpringbootShardingJdbcApplication {public static void main(String[] args) {SpringApplication.run(SpringbootShardingJdbcApplication.class, args);}}不然springboot执行test测试会报以下错误 这是因为sharding-jdbc和springboot集成的时候默认会使用SpringbootConfiguration类自动的从配置文件中读spring.shardingsphere配置但是读不到因为我们通过配置类配置的所以我们在启动类中排除SpringBootConfiguration
此方式同快速入门程序 Spring Boot properties配置。
# 定义数据源
spring.shardingsphere.datasource.names m1
spring.shardingsphere.datasource.m1.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver‐class‐name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url jdbc:mysql://localhost:3306/order_db?useUnicodetrue
spring.shardingsphere.datasource.m1.username root
spring.shardingsphere.datasource.m1.password root# 指定t_order表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key‐generator.columnorder_id
spring.shardingsphere.sharding.tables.t_order.key‐generator.typeSNOWFLAKE# 指定t_order表的数据分布情况
spring.shardingsphere.sharding.tables.t_order.actual‐data‐nodes m1.t_order_$‐{1..2}# 指定t_order表的分表策略
spring.shardingsphere.sharding.tables.t_order.table‐strategy.inline.sharding‐column order_id
spring.shardingsphere.sharding.tables.t_order.table‐strategy.inline.algorithm‐expression
t_order_$‐{order_id % 2 1}3)、Spring命名空间配置不推荐
此方式使用xml方式配置不推荐使用。
?xml version1.0 encodingUTF‐8?
beans xmlnshttp://www.springframework.org/schema/beans
xmlns:xsihttp://www.w3.org/2001/XMLSchema‐instance
xmlns:phttp://www.springframework.org/schema/p
xmlns:contexthttp://www.springframework.org/schema/context
xmlns:txhttp://www.springframework.org/schema/tx
xmlns:shardinghttp://shardingsphere.apache.org/schema/shardingsphere/sharding
xsi:schemaLocationhttp://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring‐beans.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring‐context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring‐tx.xsd
context:annotation‐config /
!‐‐定义多个数据源‐‐
bean idm1 classcom.alibaba.druid.pool.DruidDataSource destroy‐methodclose
property namedriverClassName valuecom.mysql.jdbc.Driver /
property nameurl valuejdbc:mysql://localhost:3306/order_db_1?useUnicodetrue /
property nameusername valueroot /
property namepassword valueroot /
/bean
!‐‐定义分库策略‐‐
sharding:inline‐strategy idtableShardingStrategy sharding‐columnorder_id algorithm‐
expressiont_order_$‐{order_id % 2 1} /
!‐‐定义主键生成策略‐‐
sharding:key‐generator idorderKeyGenerator typeSNOWFLAKE columnorder_id /
!‐‐定义sharding‐Jdbc数据源‐‐
sharding:data‐source idshardingDataSource
sharding:sharding‐rule data‐source‐namesm1
sharding:table‐rules
sharding:table‐rule logic‐tablet_order table‐strategy‐
reftableShardingStrategy key‐generator‐reforderKeyGenerator /
/sharding:table‐rules
/sharding:sharding‐rule
/sharding:data‐source
/beans3、Sharding-JDBC执行原理
3.1、基本概念
在了解Sharding-JDBC的执行原理前需要了解以下概念
逻辑表
水平拆分的数据表的总称。例订单数据表根据主键尾数拆分为10张表分别是 t_order_0 、 t_order_1 到 t_order_9 他们的逻辑表名为 t_order 。
真实表
在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9 。
数据节点
数据分片的最小物理单元。由数据源名称和数据表组成例 ds_0.t_order_0 。
绑定表
指分片规则一致的主表和子表。例如 t_order 表和 t_order_item 表均按照 order_id 分片,绑定表之间的分区键完全相同则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联关联查询效率将大大提升。举例说明如果SQL为
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_idi.order_id WHERE o.order_id in (10, 11);在不配置绑定表关系时假设分片键 order_id 将数值10路由至第0片将数值11路由至第1片那么路由后的SQL
应该为4条它们呈现为笛卡尔积
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);在配置绑定表关系后路由的SQL应该为2条
SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE o.order_id in (10, 11);广播表
指所有的分片数据源中都存在的表表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景例如字典表。
分片键
用于分片的数据库字段是将数据库(表)水平拆分的关键字段。例将订单表中的订单主键的尾数取模分片则订单主键为分片字段。 SQL中如果无分片字段将执行全路由性能较差。 除了对单分片字段的支持ShardingJdbc也支持根据多个字段进行分片。
分片算法
通过分片算法将数据分片支持通过 、 BETWEEN 和 IN 分片。分片算法需要应用方开发者自行实现可实现的灵活度非常高。包括精确分片算法 、范围分片算法 复合分片算法 等。例如where order_id ? 将采用精确分片算法where order_id in (?,?,?)将采用精确分片算法where order_id BETWEEN ? and ? 将采用范围分片算法复合分片算法用于分片键有多个复杂情况。
分片策略
包含分片键和分片算法由于分片算法的独立性将其独立抽离。真正可用于分片操作的是分片键 分片算法也就是分片策略。内置的分片策略大致可分为尾数取模、哈希、范围、标签、时间等。由用户方配置的分片策略则更加灵活常用的使用行表达式配置分片策略它采用Groovy表达式表示如: t_user_$-{u_id % 8} 表示t_user表根据u_id模8而分成8张表表名称为 t_user_0 到 t_user_7 。
自增主键生成策略
通过在客户端生成自增主键替换以数据库原生自增主键的方式做到分布式主键无重复。
3.2、SQL解析
当Sharding-JDBC接受到一条SQL语句时会陆续执行 SQL解析 查询优化 SQL路由 SQL改写 SQL执行 结果归并 最终返回执行结果。 SQL解析过程分为词法解析和语法解析。 词法解析器用于将SQL拆解为不可再分的原子符号称为Token。并根据不同数据库方言所提供的字典将其归类为关键字表达式字面量和操作符。 再使用语法解析器将SQL转换为抽象语法树。
例如以下SQL
SELECT id, name FROM t_user WHERE status ACTIVE AND age 18解析之后的为抽象语法树见下图 为了便于理解抽象语法树中的关键字的Token用绿色表示变量的Token用红色表示灰色表示需要进一步拆分。
最后通过对抽象语法树的遍历去提炼分片所需的上下文并标记有可能需要 SQL改写(后边介绍) 的位置。 供分片使用的解析上下文包含查询选择项Select Items、表信息Table、分片条件Sharding Condition、自增主键信息Auto increment Primary Key、排序信息Order By、分组信息Group By以及分页信息Limit、Rownum、Top。
3.3、SQL路由
SQL路由就是把针对逻辑表的数据操作映射到对数据结点操作的过程。
根据解析上下文匹配数据库和表的分片策略并生成路由路径。 对于携带分片键的SQL根据分片键操作符不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是IN)和范围路由(分片键的操作符是BETWEEN)不携带分片键的SQL则采用广播路由。根据分片键进行路由的场景可分为直接路由、标准路由、笛卡尔路由等。
标准路由
标准路由是Sharding-Jdbc最为推荐使用的分片方式它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的SQL。 当分片运算符是等于号时路由结果将落入单库表当分片运算符是BETWEEN或IN时则路由结果不一定落入唯一的库表因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。 举例说明如果按照 order_id 的奇数和偶数进行数据分片一个单表查询的SQL如下
SELECT * FROM t_order WHERE order_id IN (1, 2);那么路由的结果应为
SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);绑定表的关联查询与单表查询复杂度和性能相当。举例说明如果一个包含绑定表的关联查询的SQL如下
SELECT * FROM t_order o JOIN t_order_item i ON o.order_idi.order_id WHERE order_id IN (1, 2);那么路由的结果应为
SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE order_id IN (1, 2);可以看到SQL拆分的数目与单表是一致的。
笛卡尔路由
笛卡尔路由是最复杂的情况它无法根据绑定表的关系定位分片规则因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。 如果上个示例中的SQL并未配置绑定表关系那么路由的结果应为
SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_idi.order_id WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_idi.order_id WHERE order_id IN (1, 2);笛卡尔路由查询性能较低需谨慎使用。
全库表路由 对于不携带分片键的SQL则采取广播路由的方式。根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。其中全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作
主要包括不带分片键的DQL(数据查询)和DML数据操纵以及DDL数据定义等。例如
SELECT * FROM t_order WHERE good_prority IN (1, 10);则会遍历所有数据库中的所有表逐一匹配逻辑表和真实表名能够匹配得上则执行。路由后成为
SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);3.4、SQL改写
工程师面向逻辑表书写的SQL并不能够直接在真实的数据库中执行SQL改写用于将逻辑SQL改写为在真实数据库中可以正确执行的SQL。
如一个简单的例子若逻辑SQL为
SELECT order_id FROM t_order WHERE order_id1;假设该SQL配置分片键order_id并且order_id1的情况将路由至分片表1。那么改写之后的SQL应该为
SELECT order_id FROM t_order_1 WHERE order_id1;再比如Sharding-JDBC需要在结果归并时获取相应数据但该数据并未能通过查询的SQL返回。 这种情况主要是针对GROUP BY和ORDER BY。结果归并时需要根据 GROUP BY 和 ORDER BY 的字段项进行分组和排序但如果原始SQL的选择项中若并未包含分组项或排序项则需要对原始SQL进行改写。 先看一下原始SQL中带有结果归并所需信息的场景
SELECT order_id, user_id FROM t_order ORDER BY user_id;由于使用user_id进行排序在结果归并中需要能够获取到user_id的数据而上面的SQL是能够获取到user_id数据的因此无需补列。 如果选择项中不包含结果归并时所需的列则需要进行补列如以下SQL
SELECT order_id FROM t_order ORDER BY user_id;由于原始SQL中并不包含需要在结果归并中需要获取的user_id因此需要对SQL进行补列改写。补列之后的SQL是
SELECT order_id, user_id AS ORDER_BY_DERIVED_0 FROM t_order ORDER BY user_id;3.5、SQL执行
Sharding-JDBC采用一套自动化的执行引擎负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。 它不是简单地将SQL通过JDBC直接发送至数据源执行也并非直接将执行请求放入线程池去并发执行。它更关注平衡数据源连接创建以及内存占用所产生的消耗以及最大限度地合理利用并发等问题。 执行引擎的目标是自动化的平衡资源控制与执行效率他能在以下两种模式自适应切换
内存限制模式
使用此模式的前提是Sharding-JDBC对一次操作所耗费的数据库连接数量不做限制。 如果实际执行的SQL需要对某数据库实例中的200张表做操作则对每张表创建一个新的数据库连接并通过多线程的方式并发处理以达成执行效率最大化。
连接限制模式
使用此模式的前提是Sharding-JDBC严格控制对一次操作所耗费的数据库连接数量。 如果实际执行的SQL需要对某数据库实例中的200张表做操作那么只会创建唯一的数据库连接并对其200张表串行处理。 如果一次操作中的分片散落在不同的数据库仍然采用多线程处理对不同库的操作但每个库的每次操作仍然只创建一个唯一的数据库连接。
内存限制模式适用于OLAP操作可以通过放宽对数据库连接的限制提升系统吞吐量 连接限制模式适用于OLTP操作OLTP通常带有分片键会路由到单一的分片因此严格控制数据库连接以保证在线系统数据库资源能够被更多的应用所使用是明智的选择。
3.6、结果归并
将从各个数据节点获取的多数据结果集组合成为一个结果集并正确的返回至请求客户端称为结果归并。
Sharding-JDBC支持的结果归并从功能上可分为遍历、排序、分组、分页和聚合5种类型它们是组合而非互斥的关系。
归并引擎的整体结构划分如下图。 结果归并从结构划分可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的装饰者归并可以在流式归并和内存归并之上做进一步的处理。
内存归并很容易理解他是将所有分片结果集的数据都遍历并存储在内存中再通过统一的分组、排序以及聚合等计算之后再将其封装成为逐条访问的数据结果集返回。
流式归并是指每一次从数据库结果集中获取到的数据都能够通过游标逐条获取的方式返回正确的单条数据它与数据库原生的返回结果集的方式最为契合。
下边举例说明排序归并的过程如下图是一个通过分数进行排序的示例图它采用流式归并方式。 图中展示了3张表返回的数据结果集每个数据结果集已经根据分数排序完毕但是3个数据结果集之间是无序的。 将3个数据结果集的当前游标指向的数据值进行排序并放入优先级队列t_score_0的第一个数据值最大t_score_2的第一个数据值次之t_score_1的第一个数据值最小因此优先级队列根据t_score_0t_score_2和t_score_1的方式排序队列。 下图则展现了进行next调用的时候排序归并是如何进行的。 通过图中我们可以看到当进行第一次next调用时排在队列首位的t_score_0将会被弹出队列并且将当前游标指向的数据值也就是100返回至查询客户端并且将游标下移一位之后重新放入优先级队列。 而优先级队列也会根据t_score_0的当前数据结果集指向游标的数据值这里是90进行排序根据当前数值t_score_0排列在队列的最后一位。 之前队列中排名第二的t_score_2的数据结果集则自动排在了队列首位。
在进行第二次next时只需要将目前排列在队列首位的t_score_2弹出队列并且将其数据结果集游标指向的值返回至客户端并下移游标继续加入队列排队以此类推。 当一个结果集中已经没有数据了则无需再次加入队列。 可以看到对于每个数据结果集中的数据有序而多数据结果集整体无序的情况下Sharding-JDBC无需将所有的数据都加载至内存即可排序。 它使用的是流式归并的方式每次next仅获取唯一正确的一条数据极大的节省了内存的消耗。
装饰者归并是对所有的结果集归并进行统一的功能增强比如归并时需要聚合SUM前在进行聚合计算前都会通过内存归并或流式归并查询出结果集。因此聚合归并是在之前介绍的归并类型之上追加的归并能力即装饰者模式。
3.7、总结
通过以上内容介绍相信大家已经了解到Sharding-JDBC基础概念、核心功能以及执行原理。
基础概念逻辑表真实表数据节点绑定表广播表分片键分片算法分片策略主键生成策略
核心功能数据分片读写分离
执行流程 SQL解析 查询优化 SQL路由 SQL改写 SQL执行 结果归并 接下来我们将通过一个个demo来演示Sharding-JDBC实际使用方法。
.水平分表 前面已经介绍过水平分表是在同一个数据库内把同一个表的数据按一定规则拆到多个表中。在快速入门里我 们已经对水平分库进行实现这里不再重复介绍。
4、水平分表
前面已经介绍过水平分表是在同一个数据库内把同一个表的数据按一定规则拆到多个表中。在快速入门里我们已经对水平分库进行实现这里不再重复介绍。
5、水平分库
前面已经介绍过水平分库是把同一个表的数据按一定规则拆到不同的数据库中每个库可以放在不同的服务器上。接下来看一下如何使用Sharding-JDBC实现水平分库咱们继续对快速入门中的例子进行完善。
1)、将原有order_db库拆分为order_db_1、order_db_2 2)、分片规则修改
由于数据库拆分了两个这里需要配置两个数据源。
分库需要配置分库的策略和分表策略的意义类似通过分库策略实现数据操作针对分库的数据库进行操作。
# 以下是分片规则配置
# 数据源1
spring.shardingsphere.datasource.names m1,m2
spring.shardingsphere.datasource.m1.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m1.username root
spring.shardingsphere.datasource.m1.password root
# 数据源2
spring.shardingsphere.datasource.m2.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url jdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m2.username root
spring.shardingsphere.datasource.m2.password root# 指定t_order表的数据分布情况配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes m1.t_order_$-{1..2}# 指定t_order表的主键生成策略为SNOWFLAKE
spring.shardingsphere.sharding.tables.t_order.key-generator.column order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type SNOWFLAKE# 指定t_order表的分片策略分片策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression t_order_$-{order_id % 2 1}# 分库策略以user_id为分片键分片策略为user_id % 2 1user_id为偶数操作m1数据源否则操作m2。
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression m$‐{user_id % 2 1}# 打开sql输出日志
spring.shardingsphere.props.sql.show true分库策略定义方式如下
#分库策略如何将一个逻辑表映射到多个数据源
spring.shardingsphere.sharding.tables.逻辑表名称.database‐strategy.分片策略.分片策略属性名 #
分片策略属性值
#分表策略如何将一个逻辑表映射为多个实际表
spring.shardingsphere.sharding.tables.逻辑表名称.table‐strategy.分片策略.分片策略属性名 #分
片策略属性值Sharding-JDBC支持以下几种分片策略
不管理分库还是分表策略基本一样。
standard标准分片策略对应StandardShardingStrategy。提供对SQL语句中的, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键提供PreciseShardingAlgorithm和 RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的用于处理和IN的分片。 RangeShardingAlgorithm是可选的用于处理BETWEEN AND分片如果不配置RangeShardingAlgorithmSQL中的BETWEEN AND将按照全库路由处理。complex符合分片策略对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的, IN和 BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键由于多分片键之间的关系复 杂因此并未进行过多的封装而是直接将分片键值组合以及分片操作符透传至分片算法完全由应用开发者实现提供最大的灵活度。inline行表达式分片策略对应InlineShardingStrategy。使用Groovy的表达式提供对SQL语句中的和IN的分片操作支持只支持单分片键。对于简单的分片算法可以通过简单的配置使用从而避免繁琐的Java代码开发如: t_user_$-{u_id % 8} 表示t_user表根据u_id模8而分成8张表表名称为 t_user_0 到t_user_7 。hintHint分片策略对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。对于分片字段非SQL决定而由其他外置条件决定的场景可使用SQL Hint灵活的注入分片字段。例内部系统按照员工登录主键分库而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。none不分片策略对应NoneShardingStrategy。不分片的策略。 目前例子中都使用inline分片策略若对其他分片策略细节若感兴趣请查阅官方文档 https://shardingsphere.apache.org
3)、创建数据库
创建订单库order_db_1
CREATE DATABASE order_db_1 CHARACTER SET utf8 COLLATE utf8_general_ci;在order_db_1中创建t_order_1、t_order_2表
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;创建订单库order_db_2
CREATE DATABASE order_db_2 CHARACTER SET utf8 COLLATE utf8_general_ci;在order_db_2中创建t_order_1、t_order_2表
DROP TABLE IF EXISTS t_order_1;
CREATE TABLE t_order_1 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS t_order_2;
CREATE TABLE t_order_2 (order_id bigint(20) NOT NULL COMMENT 订单id,price decimal(10, 2) NOT NULL COMMENT 订单价格,user_id bigint(20) NOT NULL COMMENT 下单用户id,status varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 订单状态,PRIMARY KEY (order_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;4)、插入测试
修改testInsertOrder方法插入数据中包含不同的user_id /*** 水平分库测试*/Testpublic void testInsertOrderByVSDB() {for (int i 0; i 10; i) {orderDao.insertOrder(new BigDecimal((i 1) * 5), 1L, SUCCESS);}for (int i 0; i 10; i) {orderDao.insertOrder(new BigDecimal((i 1) * 10), 2L, SUCCESS);}}执行testInsertOrder:
通过日志可以看出根据user_id的奇偶不同数据分别落在了不同数据源达到目标。
5)、查询测试
调用水平分库查询的接口进行测试 /*** 水平分库查询测试*/Testpublic void testSelectOrderVSDBByIds() {ListLong ids new ArrayList();ids.add(1138603491963437056L);ids.add(1138603491678224385L);ListMap maps orderDao.selectOrderbyIds(ids);System.out.println(maps);}通过日志发现sharding-jdbc将sql都路由到m1 问题分析
由于我们sharding-jdbc配置数据节点只配置了m1所以看到的sharding-jdbc将sql都路由到m1 解决办法
我们sharding-jdbc配置数据节点配置m1m2
# 指定t_order表的数据分布情况配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes m$-{1..2}.t_order_$-{1..2}我们再次调用水平分库查询的接口进行测试 由于查询语句中并没有使用分片键user_id所以sharding-jdbc将广播路由到每个数据结点。
下边我们在sql中添加分片键进行查询。
在OrderDao中定义接口 Select({script, select, * , from t_order t ,where t.order_id in,foreach collectionorderIds itemid open( separator, close),#{id},/foreach, and t.user_id #{userId} ,/script})ListMap selectOrderbyUserAndIds(Param(userId) Integer userId, Param(orderIds) ListLong orderIds);编写测试方法 Testpublic void testSelectOrderbyUserAndIds() {ListLong orderIds new ArrayList();orderIds.add(1138603491963437056L);orderIds.add(1138603491678224385L);//查询条件中包括分库的键user_idint user_id 1;ListMap orders orderDao.selectOrderbyUserAndIds(user_id, orderIds);JSONArray jsonOrders new JSONArray(orders);System.out.println(jsonOrders);}查询条件user_id为1根据分片策略m$-{user_id % 2 1}计算得出m2此sharding-jdbc将sql路由到m2见上图日志。
6、垂直分库
前面已经介绍过垂直分库是指按照业务将表进行分类分布到不同的数据库上面每个库可以放在不同的服务器上它的核心理念是专库专用。接下来看一下如何使用Sharding-JDBC实现垂直分库。
1)、创建数据库
创建数据库user_db
CREATE DATABASE user_db CHARACTER SET utf8 COLLATE utf8_general_ci;在user_db中创建t_user表
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
user_id bigint(20) NOT NULL COMMENT 用户id,
fullname varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 用户姓名,
user_type char(1) DEFAULT NULL COMMENT 用户类型,
PRIMARY KEY (user_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;2)、在Sharding-JDBC规则中修改
# 新增m0数据源对应user_db
spring.shardingsphere.datasource.names m0,m1,m2
# 数据源0
spring.shardingsphere.datasource.m0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url jdbc:mysql://localhost:3306/order_db_1?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m0.username root
spring.shardingsphere.datasource.m0.password root....
# t_user分表策略固定分配至m0的t_user真实表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes m$-{0}.t_user
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.sharding-column user_id
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.algorithm-expression m$-{0}3)、数据操作
新增UserDao:
Mapper
public interface UserDao {/*** 新增用户* param userId 用户id* param fullname 用户姓名* return*/Insert(insert into t_user(user_id, fullname) value(#{userId},#{fullname}))int insertUser(Param(userId)Long userId, Param(fullname)String fullname);/*** 根据id列表查询多个用户* param userIds 用户id列表* return*/Select({script, select, * , from t_user t , where t.user_id in,foreach collectionuserIds itemid open( separator, close),#{id},/foreach,/script})ListMap selectUserbyIds(Param(userIds) ListLong userIds);
}执行testInsertUser: 通过日志可以看出t_user表的数据被落在了m0数据源达到目标。
执行testSelectUserbyIds: 通过日志可以看出t_user表的查询操作被落在了m0数据源达到目标
4)、测试 新增单元测试方法
7、公共表
公共表属于系统中数据量较小变动少而且属于高频联合查询的依赖表。参数表、数据字典表等属于此类型。可以将这类表在每个数据库都保存一份所有更新操作都同时发送到所有分库执行。接下来看一下如何使用Sharding-JDBC实现公共表。
1)、创建数据库 分别在user_db、order_db_1、order_db_2中创建t_dict表
CREATE TABLE t_dict (dict_id BIGINT ( 20 ) NOT NULL COMMENT 字典id,type VARCHAR ( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 字典类型,code VARCHAR ( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 字典编码,value VARCHAR ( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 字典值,PRIMARY KEY ( dict_id ) USING BTREE
) ENGINE INNODB CHARACTER
SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;2)、在Sharding-JDBC规则中修改
# 指定t_dict为公共表
spring.shardingsphere.sharding.broadcast‐tablest_dict3)、数据操作
新增DictDao
Mapper
public interface DictDao {/*** 新增字典** param dictId id* param type 字典类型* param code 字典编码* param value 字典值* return*/Insert(insert into t_dict(dict_id, type, code, value) value(#{dictId}, #{type}, #{code}, #{value}))int insertDict(Param(dictId) Long dictId, Param(type) String type, Param(code) Stringcode, Param(value) String value);/*** 删除字典** param dictId 字典id* return*/Delete(delete from t_dict where dict_id #{dictId})int deleteDict(Param(dictId) Long dictId);
}4)、字典操作测试
新增单元测试方法 Testpublic void testInsertDict(){dictDao.insertDict(1L,user_type,0,管理员);dictDao.insertDict(2L,user_type,1,操作员);}Testpublic void testDeleteDict(){dictDao.deleteDict(1L);dictDao.deleteDict(2L);}执行testInsertDict 通过日志可以看出对t_dict的表的操作被广播至所有数据源。
测试删除字典观察是否把所有数据源中该 公共表的记录删除。
5)、字典关联查询测试
字典表已在各各分库存在各业务表即可和字典表关联查询。
定义用户关联查询dao
在UserDao中定义 /*** 根据id列表查询多个用户关联查询字典表** param userIds 用户id列表* return*/Select({script, select, * , from t_user t ,t_dict b, where t.user_type b.code and t.user_id in,foreach collectionuserIds itemid open( separator, close),#{id},/foreach,/script})ListMap selectUserInfobyIds(Param(userIds) ListLong userIds);定义测试方法 Testpublic void testSelectUserInfobyIds(){ListLong userIds new ArrayList();userIds.add(1L);userIds.add(2L);ListMap users dictDao.selectUserInfobyIds(userIds);JSONArray jsonUsers new JSONArray(users);System.out.println(jsonUsers);}执行测试方法查看日志成功关联查询字典表 8、读写分离
8.1、理解读写分离
面对日益增加的系统访问量数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说将数据库拆分为主库和从库主库负责处理事务性的增删改操作从库负责处理查询操作能够有效的避免由数据更新导致的行锁使得整个系统的查询性能得到极大的改善。 通过一主多从的配置方式可以将查询请求均匀的分散到多个数据副本能够进一步的提升系统的处理能力。 使用多主多从的方式不但能够提升系统的吞吐量还能够提升系统的可用性可以达到在任何一个数据库宕机甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。 读写分离的数据节点中的数据内容是一致的而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用能够更加有效的提升系统的性能。
Sharding-JDBC读写分离则是根据SQL语义的分析将读操作和写操作分别路由至主库与从库。它提供透明化读写分离让使用方尽量像使用一个数据库一样使用主从数据库集群。 sharding-JDBC提供一主多从的读写分离配置可独立使用也可配合分库分表使用同一线程且同一数据库连接内如有写入操作以后的读操作均从主库读取用于保证数据一致性。Sharding-JDBC不提供主从数据库的数据同步功能需要采用其他机制支持。 接下来咱们对上面例子中user_db进行读写分离实现。为了实现Sharding-JDBC的读写分离首先要进行 mysql的主从同步配置。
8.2、mysql主从同步(docker)
8.2.1、新建主服务器容器实例3307 下载 mysql 镜像 docker pull mysql:5.7创建 Master 实例并启动 docker run -p 3307:3306 --name mysql-master \
-v /mydata/mysql/mysql-master/log:/var/log/mysql \
-v /mydata/mysql/mysql-master/data:/var/lib/mysql \
-v /mydata/mysql/mysql-master/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORDroot \
-d mysql:5.7参数说明 -p 3307:3306将容器的 3306 端口映射到主机的 3307 端口-v /mydata/mysql/mysql-master/conf:/etc/mysql将配置文件夹挂在到主机-v /mydata/mysql/mysql-master/log:/var/log/mysql将日志文件夹挂载到主机-v /mydata/mysql/mysql-master/data:/var/lib/mysql/将配置文件夹挂载到主机-e MYSQL_ROOT_PASSWORDroot初始化 root 用户的密码 修改 master 基本配置 进入/mydata/mysql/mysql-master/conf目录下新建my.cnf vim /mydata/mysql/mysql-master/conf/my.cnf[mysqld]
## 设置server_id同一局域网中需要唯一
server_id101
## 指定不需要同步的数据库名称
binlog-ignore-dbmysql
## 开启二进制日志功能
log-binmall-mysql-bin
## 设置二进制日志使用内存大小事务
binlog_cache_size1M
## 设置使用的二进制日志格式mixed,statement,row
binlog_formatmixed
## 二进制日志过期清理时间。默认值为0表示不自动清理。
expire_logs_days7
## 跳过主从复制中遇到的所有错误或指定类型的错误避免slave端复制中断。
## 如1062错误是指一些主键重复1032错误是因为主从数据库数据不一致。
slave_skip_errors1062修改完配置后重启master实例 docker restart mysql-master进入mysql-master容器 docker exec -it mysql-master /bin/bashmysql -uroot -prootmaster容器实例内创建数据同步用户 创建一个具有连接数据库权限的用户 ‘slave’并设置了密码为 ‘123456’允许从任何主机连接到MySQL数据库 CREATE USER slave% IDENTIFIED BY 123456;授予了用户 ‘slave’ 在任何主机上执行MySQL数据库复制操作和查看复制状态的权限。这是配置MySQL主从复制所必需的权限之一 GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO slave%;8.2.2、新建从服务器容器实例3308 创建 slave实例并启动 docker run -p 3308:3306 --name mysql-slave \
-v /mydata/mysql/mysql-slave/log:/var/log/mysql \
-v /mydata/mysql/mysql-slave/data:/var/lib/mysql \
-v /mydata/mysql/mysql-slave/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORDroot \
-d mysql:5.7修改 slave基本配置 进入/mydata/mysql/mysql-slave/conf目录下新建my.cnf vim /mydata/mysql/mysql-slave/conf/my.cnf[mysqld]
## 设置server_id同一局域网中需要唯一
server_id102
## 指定不需要同步的数据库名称
binlog-ignore-dbmysql
## 开启二进制日志功能以备Slave作为其它数据库实例的Master时使用
log-binmall-mysql-slave1-bin
## 设置二进制日志使用内存大小事务
binlog_cache_size1M
## 设置使用的二进制日志格式mixed,statement,row
binlog_formatmixed
## 二进制日志过期清理时间。默认值为0表示不自动清理。
expire_logs_days7
## 跳过主从复制中遇到的所有错误或指定类型的错误避免slave端复制中断。
## 如1062错误是指一些主键重复1032错误是因为主从数据库数据不一致
slave_skip_errors1062
## relay_log配置中继日志
relay_logmall-mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates1
## slave设置为只读具有super权限的用户除外
read_only1修改完配置后重启slave实例 docker restart mysql-slave在主数据库中查看主从同步状态 show master status;进入mysql-slave容器 docker exec -it mysql-slave /bin/bashmysql -uroot -proot在从数据库中配置主从复制 change master to master_host192.168.119.128, master_userslave, master_password123456, master_port3307, master_log_filemall-mysql-bin.000001, master_log_pos154, master_connect_retry30;主从复制命令参数说明: master_host主数据库的IP地址master_port主数据库的运行端口master_user在主数据库创建的用于同步数据的用户账号master_password在主数据库创建的用于同步数据的用户密码master_log_file指定从数据库要复制数据的日志文件通过查看主数据的状态获取File参数master_log_pos指定从数据库从哪个位置开始复制数据通过查看主数据的状态获取Position参数master_connect_retry连接失败重试的时间间隔单位为秒。 在从数据库中查看主从同步状态 show slave status \G;在从数据库中开启主从同步 start slave;查看从数据库状态发现已经同步 show slave status \G;主从复制测试 主机新建库-使用库-新建表-插入数据ok 从机使用库-查看记录ok
8.3.3、创建主从同步的数据库和表
创建数据库user_db
CREATE DATABASE user_db CHARACTER SET utf8 COLLATE utf8_general_ci;在user_db中创建t_user表
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
user_id bigint(20) NOT NULL COMMENT 用户id,
fullname varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 用户姓名,
user_type char(1) DEFAULT NULL COMMENT 用户类型,
PRIMARY KEY (user_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;8.3、实现sharding-jdbc读写分离
1)、在Sharding-JDBC规则中修改 # 增加数据源s0使用上面主从同步配置的从库。
spring.shardingsphere.datasource.names m0,m1,m2,s0
# 数据源0
spring.shardingsphere.datasource.m0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url jdbc:mysql://192.168.119.128:3307/user_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m0.username root
spring.shardingsphere.datasource.m0.password rootspring.shardingsphere.datasource.s0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver‐class‐name com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s0.url jdbc:mysql://192.168.119.128:3308/user_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.s0.username root
spring.shardingsphere.datasource.s0.password root
....
# 主库从库逻辑数据源定义 ds0为user_db
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name m0
spring.shardingsphere.sharding.master‐slave‐rules.ds0.slave-data-source-names s0
# t_user分表策略固定分配至ds0的t_user真实表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes ds0.t_user# t_user分表策略固定分配至m0的t_user真实表
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.sharding-column user_id
spring.shardingsphere.sharding.tables.t_user.database-strategy.inline.algorithm-expression ds0
....2)、测试 执行testInsertUser单元测试 通过日志可以看出所有写操作落入m0数据源。
执行testSelectUserbyIds单元测试 通过日志可以看出所有写操作落入s0数据源达到目标。
9、案例
9.1、需求描述
电商平台商品列表展示每个列表项中除了包含商品基本信息、商品描述信息之外还包括了商品所属的店铺信息如下 案例实现功能如下 1、添加商品 2、商品分页查询 4、商品统计
9.2、数据库设计
数据库设计如下其中商品与店铺信息之间进行了垂直分库分为了PRODUCT_DB(商品库)和STORE_DB(店铺库)商品信息还进行了垂直分表分为了商品基本信息(product_info)和商品描述信息(product_descript)地理区域信息(region)作为公共表冗余在两库中 考虑到商品信息的数据增长性对PRODUCT_DB(商品库)进行了水平分库分片键使用店铺id分片策略为店铺 ID%2 1因此商品描述信息对所属店铺ID进行了冗余
对商品基本信息(product_info)和商品描述信息(product_descript)进行水平分表分片键使用商品id分片策略为商品ID%2 1,并将为这两个表设置为绑定表避免笛卡尔积join
为避免主键冲突ID生成策略采用雪花算法来生成全局唯一ID最终数据库设计为下图 求使用读写分离来提升性能可用性。
9.3、环境说明
操作系统Win10数据库MySQL-5.7.25JDK64位 jdk1.8.0_201应用框架spring-boot-2.7.6mybatis-plus 3.5.3.1Sharding-JDBCsharding-jdbc-spring-boot-starter-4.0.0-RC1
9.4、环境准备
9.4.1、mysql主从同步(docker)
参考读写分离章节对以下库进行主从同步配置
# 设置需要同步的数据库
binlog‐do‐dbstore_db
binlog‐do‐dbproduct_db_1
binlog‐do‐dbproduct_db_29.4.2、初始化数据库
创建store_db数据库并执行以下脚本创建表
创建数据库store_db
CREATE DATABASE store_db CHARACTER SET utf8 COLLATE utf8_general_ci;DROP TABLE IF EXISTS region;
CREATE TABLE region (
id bigint(20) NOT NULL COMMENT id,
region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
地理区域编码,
region_name varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 地理区域名称,
level tinyint(1) NULL DEFAULT NULL COMMENT 地理区域级别(省、市、县),
parent_region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 上级地理区域编码,
PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;
INSERT INTO region VALUES (1, 110000, 北京, 0, NULL);
INSERT INTO region VALUES (2, 410000, 河南省, 0, NULL);
INSERT INTO region VALUES (3, 110100, 北京市, 1, 110000);
INSERT INTO region VALUES (4, 410100, 郑州市, 1, 410000);
DROP TABLE IF EXISTS store_info;
CREATE TABLE store_info (
id bigint(20) NOT NULL COMMENT id,
store_name varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
店铺名称,
reputation int(11) NULL DEFAULT NULL COMMENT 信誉等级,
region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
店铺所在地,
PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;
INSERT INTO store_info VALUES (1, XX零食店, 4, 110100);
INSERT INTO store_info VALUES (2, XX饮品店, 3, 410100);创建product_db_1、product_db_2数据库并分别对两库执行以下脚本创建表
创建数据库product_db_1
CREATE DATABASE product_db_1 CHARACTER SET utf8 COLLATE utf8_general_ci;创建数据库product_db_2
CREATE DATABASE product_db_2 CHARACTER SET utf8 COLLATE utf8_general_ci;DROP TABLE IF EXISTS product_descript_1;
CREATE TABLE product_descript_1 (
id bigint(20) NOT NULL COMMENT id,
product_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属商品id,
descript longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 商品描述,
store_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属店铺id,
PRIMARY KEY (id) USING BTREE,
INDEX FK_Reference_2(product_info_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS product_descript_2;
CREATE TABLE product_descript_2 (
id bigint(20) NOT NULL COMMENT id,
product_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属商品id,
descript longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT 商品描述,
store_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属店铺id,
PRIMARY KEY (id) USING BTREE,
INDEX FK_Reference_2(product_info_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS product_info_1;
CREATE TABLE product_info_1 (
product_info_id bigint(20) NOT NULL COMMENT id,
store_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属店铺id,
product_name varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 商品名称,
spec varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 规
格,
region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
产地,
price decimal(10, 0) NULL DEFAULT NULL COMMENT 商品价格,
image_url varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
商品图片,
PRIMARY KEY (product_info_id) USING BTREE,
INDEX FK_Reference_1(store_info_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;DROP TABLE IF EXISTS product_info_2;
CREATE TABLE product_info_2 (
product_info_id bigint(20) NOT NULL COMMENT id,
store_info_id bigint(20) NULL DEFAULT NULL COMMENT 所属店铺id,
product_name varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 商品名称,
spec varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 规
格,
region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
产地,
price decimal(10, 0) NULL DEFAULT NULL COMMENT 商品价格,
image_url varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
商品图片,
PRIMARY KEY (product_info_id) USING BTREE,
INDEX FK_Reference_1(store_info_id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;
DROP TABLE IF EXISTS region;CREATE TABLE region (
id bigint(20) NOT NULL COMMENT id,
region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT
地理区域编码,
region_name varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 地理区域名称,
level tinyint(1) NULL DEFAULT NULL COMMENT 地理区域级别(省、市、县),
parent_region_code varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT 上级地理区域编码,
PRIMARY KEY (id) USING BTREE
) ENGINE InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT Dynamic;
INSERT INTO region VALUES (1, 110000, 北京, 0, NULL);
INSERT INTO region VALUES (2, 410000, 河南省, 0, NULL);
INSERT INTO region VALUES (3, 110100, 北京市, 1, 110000);
INSERT INTO region VALUES (4, 410100, 郑州市, 1, 410000);9.5、实现步骤
9.5.1、搭建maven工程
1搭建工程maven工程shopping导入资料中基础代码shopping以dbsharding为总体父工程并做好 spring boot相关配置。 2引入maven依赖
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.6/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersionartifactIdshopping/artifactIddependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdoptionaltrue/optional/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion1.2.18/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3.1/version/dependencydependencygroupIdorg.apache.shardingsphere/groupIdartifactIdsharding-jdbc-spring-boot-starter/artifactIdversion4.0.0-RC1/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger2/artifactIdversion2.10.1/version/dependencydependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger-ui/artifactIdversion2.10.1/version/dependency/dependencies/project9.5.2、分片配置
既然是分库分表那么就需要定义多个真实数据源每一个数据库链接信息就是一个数据源定义如
spring.shardingsphere.datasource.m0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.url jdbc:mysql://192.168.119.128:3307/store_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m0.username root
spring.shardingsphere.datasource.m0.password rootm0就是这个真实数据源的名称然后需要告诉Sharding-JDBC咱们有哪些真实数据源如
spring.shardingsphere.datasource.names m0,m1,m2,s0,s1,s2如果需要配置读写分离还需要告诉Sharding-JDBC这么多真实数据源那几个是一套读写分离也就是定义主从逻辑数据源
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names s0我们已经对m0和s0做了mysql主从同步那我们需要告诉Sharding-JDBCm0、s0为一组主从同步数据源其 中m0为主s0为从并且定义名称为ds0这个ds0就是主从逻辑数据源。
最终配置如下具体的分库分表策略参考注释内容
server.port56082spring.application.name shopping
spring.profiles.active localserver.servlet.context-path /shopping
server.servlet.encoding.enabled true
server.servlet.encoding.charset UTF-8
server.servlet.encoding.force truemybatis.configuration.map-underscore-to-camel-case true#sharding-jdbc分片规则
#配置数据源 m0,m1,m2,s0,s1,s2
spring.shardingsphere.datasource.names m0,m1,m2,s0,s1,s2spring.shardingsphere.datasource.m0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m0.url jdbc:mysql://192.168.119.128:3307/store_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m0.username root
spring.shardingsphere.datasource.m0.password rootspring.shardingsphere.datasource.m1.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url jdbc:mysql://192.168.119.128:3307/product_db_1?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m1.username root
spring.shardingsphere.datasource.m1.password rootspring.shardingsphere.datasource.m2.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m2.url jdbc:mysql://192.168.119.128:3307/product_db_2?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.m2.username root
spring.shardingsphere.datasource.m2.password rootspring.shardingsphere.datasource.s0.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s0.url jdbc:mysql://192.168.119.128:3308/store_db?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.s0.username root
spring.shardingsphere.datasource.s0.password rootspring.shardingsphere.datasource.s1.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s1.url jdbc:mysql://192.168.119.128:3308/product_db_1?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.s1.username root
spring.shardingsphere.datasource.s1.password rootspring.shardingsphere.datasource.s2.type com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s2.driver-class-name com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s2.url jdbc:mysql://192.168.119.128:3308/product_db_2?useUnicodetruecharacterEncodingutf-8useSSLtrueserverTimezoneAsia/Shanghai
spring.shardingsphere.datasource.s2.username root
spring.shardingsphere.datasource.s2.password root#主从关系
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names s0
spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name m1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names s1
spring.shardingsphere.sharding.master-slave-rules.ds2.master-data-source-name m2
spring.shardingsphere.sharding.master-slave-rules.ds2.slave-data-source-names s2#分库策略水平
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column store_info_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression ds$-{store_info_id % 2 1}#分表策略
# store_info分表策略
spring.shardingsphere.sharding.tables.store_info.actual-data-nodes ds$-{0}.store_info
spring.shardingsphere.sharding.tables.store_info.table-strategy.inline.sharding-column id
spring.shardingsphere.sharding.tables.store_info.table-strategy.inline.algorithm-expression store_info# product_info分表策略
#数据结点包括ds1.product_info_1ds1.product_info_2,ds2.product_info_1ds2.product_info_2
spring.shardingsphere.sharding.tables.product_info.actual-data-nodes ds$-{1..2}.product_info_$-{1..2}
spring.shardingsphere.sharding.tables.product_info.table-strategy.inline.sharding-column product_info_id
spring.shardingsphere.sharding.tables.product_info.table-strategy.inline.algorithm-expression product_info_$-{product_info_id%21}
spring.shardingsphere.sharding.tables.product_info.key-generator.column product_info_id
spring.shardingsphere.sharding.tables.product_info.key-generator.type SNOWFLAKE#product_descript分表策略
spring.shardingsphere.sharding.tables.product_descript.actual-data-nodes ds$-{1..2}.product_descript_$-{1..2}
spring.shardingsphere.sharding.tables.product_descript.table-strategy.inline.sharding-column product_info_id
spring.shardingsphere.sharding.tables.product_descript.table-strategy.inline.algorithm-expression product_descript_$-{product_info_id % 2 1}
spring.shardingsphere.sharding.tables.product_descript.key-generator.column id
spring.shardingsphere.sharding.tables.product_descript.key-generator.type SNOWFLAKE# 设置product_info,product_descript为绑定表
spring.shardingsphere.sharding.binding-tables[0] product_info,product_descript# 设置region为广播表(公共表)每次更新操作会发送至所有数据源
spring.shardingsphere.sharding.broadcast-tables region# 打开sql输出日志
spring.shardingsphere.props.sql.show trueswagger.enable truelogging.level.root info
logging.level.org.springframework.web info
logging.level.com.itheima.dbsharding debug9.5.3、添加商品
实体类
参考基础工程
DAO实现
添加“com.itheima.shopping.dao.ProductDao”类代码如下
Mapper
public interface ProductDao {/*** 添加商品基本信息** param productInfo* return*/Insert(insert into product_info(store_info_id,product_name,spec,region_code,price) values (#{storeInfoId},#{productName},#{spec},#{regionCode},#{price}))Options(useGeneratedKeys true, keyProperty productInfoId, keyColumn product_info_id)int insertProductInfo(ProductInfo productInfo);/*** 添加商品描述信息** param productDescript* return*/Insert(insert into product_descript(product_info_id,descript,store_info_id) value(#{productInfoId},#{descript},#{storeInfoId}))Options(useGeneratedKeys true, keyProperty id, keyColumn id)int insertProductDescript(ProductDescript productDescript);Select(select i.*,d.descript,r.region_name placeOfOrigin from product_info i join product_descript d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit #{start},#{pageSize})ListProductInfo selectProductList(Param(start) int start, Param(pageSize) int pageSize);
}service实现
针对垂直分库的两个库分别实现店铺服务、商品服务
添加“com.itheima.shopping.service.ProductService”类代码如下
public interface ProductService {/*** 添加商品** param product*/void createProduct(ProductInfo product);
}添加“com.itheima.shopping.service.impl.ProductServiceImpl”类代码如下
Service
public class ProductServiceImpl implements ProductService {AutowiredProductDao productDao;/*** 添加商品** param productInfo 商品信息*/OverrideTransactionalpublic void createProduct(ProductInfo productInfo) {ProductDescript productDescript new ProductDescript();// 调用dao向商品信息表插入数据productDao.insertProductInfo(productInfo);// 将商品信息id设置到productDescriptproductDescript.setProductInfoId(productInfo.getProductInfoId());// 设置店铺idproductDescript.setStoreInfoId(productInfo.getStoreInfoId());// 设置商品描述信息productDescript.setDescript(productInfo.getDescript());// 向商品描述信息表插入数据productDao.insertProductDescript(productDescript);}
}controller实现 添加“com.itheima.shopping.controller.SellerController”类代码如下
RestController
public class SellerController {Autowiredprivate ProductService productService;PostMapping(/products)public String createProject(RequestBody ProductInfo productInfo) {productService.createProduct(productInfo);return 创建成功!;}
}单元测试
SpringBootTest
public class ShardingTest {AutowiredProductService productService;AutowiredProductDao productDao;//添加商品Testpublic void testCreateProduct() {for (int i 1; i 10; i) {ProductInfo productInfo new ProductInfo();productInfo.setStoreInfoId(2L);//店铺idproductInfo.setProductName(Java编程思想 i);//商品名称productInfo.setSpec(大号);productInfo.setPrice(new BigDecimal(60));productInfo.setRegionCode(110100);productInfo.setDescript(Java编程思想不错 i);//商品描述productService.createProduct(productInfo);}}
}这里使用了sharding-jdbc所提供的全局主键生成方式之一雪花算法来生成全局业务唯一主键。 通过添加商品接口新增商品进行分库验证store_info_id为偶数的数据在product_db_1, 为奇数的数据在 product_db_2。 9.5.4、查询商品
Dao实现
在ProductDao中定义商品查询方法 修改“com.itheima.shopping.dao.ProductDao”类代码如下 /*** 查询商品列表** param start* param pageSize* return*/Select(select i.*,d.descript,r.region_name placeOfOrigin from product_info i join product_descript d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit #{start},#{pageSize})ListProductInfo selectProductList(Param(start) int start, Param(pageSize) int pageSize);Service实现
在ProductServiceImpl定义商品查询方法
修改“com.itheima.shopping.service.ProductService”类代码如下 /*** 查询商品** param page* param pageSize* return*/ListProductInfo queryProduct(int page, int pageSize);修改“com.itheima.shopping.service.impl.ProductServiceImpl”类代码如下 Overridepublic ListProductInfo queryProduct(int page, int pageSize) {int start (page - 1) * pageSize;return productDao.selectProductList(start, pageSize);}Controller实现 修改“com.itheima.shopping.controller.SellerController”类代码如下 GetMapping(value /products/{page}/{pageSize})public ListProductInfo queryProduct(PathVariable(page) int page, PathVariable(pageSize) int pageSize) {return productService.queryProduct(page, pageSize);}单元测试 /*** 查询商品*/Testpublic void testSelectProductList() {ListProductInfo productInfos productService.queryProduct(1, 10);System.out.println(productInfos);}[
ProductInfo(productInfoId1141029439648301057, storeInfoId2, productNameJava编程思想9, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错9, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull), ProductInfo(productInfoId1141029439623135232, storeInfoId2, productNameJava编程思想8, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错8, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439606358017, storeInfoId2, productNameJava编程思想7, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错7, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439581192192, storeInfoId2, productNameJava编程思想6, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错6, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439560220673, storeInfoId2, productNameJava编程思想5, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错5, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439539249152, storeInfoId2, productNameJava编程思想4, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错4, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439522471937, storeInfoId2, productNameJava编程思想3, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错3, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029439493111808, storeInfoId2, productNameJava编程思想2, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错2, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull),
ProductInfo(productInfoId1141029438796857345, storeInfoId2, productNameJava编程思想1, spec大号, regionCode110100, price60, imageUrlnull, descriptJava编程思想不错1, placeOfOrigin北京市, storeNamenull, reputation0, storeRegionNamenull)
]通过查询商品列表接口能够查询到所有分片的商品信息关联的地理区域店铺信息正确。
如果我们查询第2页每页2条数据可以看到sharding-jdbc是从0开开始查查询4条排序好之后返回对应的数据。
Rule Type: shardingLogic SQL: select i.*,d.descript,r.region_name placeOfOrigin from product_info i join product_descript d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit ?,?Actual SQL: s1 ::: select i.*,d.descript,r.region_name placeOfOrigin from product_info_1 i join product_descript_1 d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit ?,? ::: [0, 4]Actual SQL: s1 ::: select i.*,d.descript,r.region_name placeOfOrigin from product_info_2 i join product_descript_2 d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit ?,? ::: [0, 4]Actual SQL: s2 ::: select i.*,d.descript,r.region_name placeOfOrigin from product_info_1 i join product_descript_1 d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit ?,? ::: [0, 4]Actual SQL: s2 ::: select i.*,d.descript,r.region_name placeOfOrigin from product_info_2 i join product_descript_2 d on i.product_info_id d.product_info_id join region r on i.region_code r.region_code order by product_info_id desc limit ?,? ::: [0, 4]总结
分页查询是业务中最常见的场景Sharding-jdbc支持常用关系数据库的分页查询不过Sharding-jdbc的分页功能比较容易让使用者误解用户通常认为分页归并会占用大量内存。 在分布式的场景中将 LIMIT 10000000 , 10改写为 LIMIT 0, 10000010 才能保证其数据的正确性。 用户非常容易产生ShardingSphere会将大量无意义的数据加载至内存中造成内存溢出风险的错觉。 其实大部分情况都通过流式归并获取数据结果集因此ShardingSphere会通过结果集的next方法将无需取出的数据全部跳过并不会将其存入内存。
但同时需要注意的是由于排序的需要大量的数据仍然需要传输到Sharding-Jdbc的内存空间。 因此采用LIMIT这种方式分页并非最佳实践。 由于LIMIT并不能通过索引查询数据因此如果可以保证ID的连续性通过ID进行分页是比较好的解决方案例如
SELECT * FROM t_order WHERE id 100000 AND id 100010 ORDER BY id;或通过记录上次查询结果的最后一条记录的ID进行下一页的查询例如
SELECT * FROM t_order WHERE id 10000000 LIMIT 10;排序功能是由Sharding-jdbc的排序归并来完成由于在SQL中存在 ORDER BY 语句因此每个数据结果集自身是有序的因此只需要将数据结果集当前游标指向的数据值进行排序即可。 这相当于对多个有序的数组进行排序归并排序是最适合此场景的排序算法。
9.5.5、统计商品
本小节实现商品总数统计商品分组统计
Dao实现 修改“com.itheima.shopping.dao.ProductDao”类代码如下 在ProductDao中定义 /*** 商品总数** return*/Select(select count(1) from product_info)int selectCount();/*** 商品分组统计** return*/Select(select t.region_code,count(1) as num from product_info t group by t.region_code having num 1 order by region_code )ListMap selectProductGroupList();单元测试 /*** 统计商品总数*/Testpublic void testSelectCount() {int i productDao.selectCount();System.out.println(i);}/*** 分组统计商品*/Testpublic void testSelectProductGroupList() {ListMap maps productDao.selectProductGroupList();System.out.println(maps);}结果
总结
分组统计
分组统计也是业务中常见的场景分组功能的实现由Sharding-jdbc分组归并完成。分组归并的情况最为复杂它分为流式分组归并和内存分组归并。 流式分组归并要求SQL的排序项与分组项的字段必须保持一致否则只能通过内存归并才能保证其数据的正确性。
举例说明假设根据科目分片表结构中包含考生的姓名为了简单起见不考虑重名的情况和分数。通过SQL获取每位考生的总分可通过如下SQL
SELECT name, SUM(score) FROM t_score GROUP BY name ORDER BY name;在分组项与排序项完全一致的情况下取得的数据是连续的分组所需的数据全数存在于各个数据结果集的当前游标所指向的数据值因此可以采用流式归并。如下图所示。 进行归并时逻辑与排序归并类似。 下图展现了进行next调用的时候流式分组归并是如何进行的。 通过图中我们可以看到当进行第一次next调用时排在队列首位的t_score_java将会被弹出队列并且将分组值同为“Jetty”的其他结果集中的数据一同弹出队列。 在获取了所有的姓名为“Jetty”的同学的分数之后进行累加操作那么在第一次next调用结束后取出的结果集是“Jetty”的分数总和。 与此同时所有的数据结果集中的游标都将下移至数据值“Jetty”的下一个不同的数据值并且根据数据结果集当前游标指向的值进行重排序。 因此包含名字顺着第二位的“John”的相关数据结果集则排在的队列的前列。
10、课程总结
重点知识回顾 为什么分库分表分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题将原来独立的数据库拆分成若干数据库组成 将数据大表拆分成若干数据表组成使得单一数据库、单一数据表的数据量变小从而达到提升数据库性能的目的。
分库分表方式垂直分表、垂直分库、水平分库、水平分表
分库分表带来问题由于数据分散在多个数据库服务器导致了事务一致性问题、跨节点join问题、跨节点分页、排序、函数主键需要全局唯一公共表。
Sharding-JDBC基础概念逻辑表真实表数据节点绑定表广播表分片键分片算法分片策略主键生成策略
Sharding-JDBC核心功能数据分片读写分离
Sharding-JDBC执行流程 SQL解析 查询优化 SQL路由 SQL改写 SQL执行 结果归并
最佳实践
系统在设计之初就应该对业务数据的耦合松紧进行考量从而进行垂直分库、垂直分表使数据层架构清晰明了。若非必要无需进行水平切分应先从缓存技术着手降低对数据库的访问压力。如果缓存使用过后数据库访问量还是非常大可以考虑数据库读、写分离原则。若当前数据库压力依然大且业务数据持续增长无法估量最后可考虑水平分库、分表单表拆分数据控制在1000万以内。