行业知识
ShardingSphere系列01:Shardingjdbc实现分表(含项目实践)
使用目的
为什么使用需要分库分表,在数据量比较小的时候,是不需要考虑使用分库分表的,一般而言,使用单库和单表就ok了,但是使用Mysql,当数据量达到400w~500w的查询时,查询速度便慢了很多了,因此就需要分库和分表了
在《阿里开发手册嵩山版》中有写到:
而分库分表中,有垂直分片和水平分片之说,可以简单理解,垂直分片,就是将一个大表,拆分成多个小表。例如将一张大的订单表分为多个表;水平分表,就是同个表,拆分成多个一样的表进行存储。例如可以按照时间进行拆分,也可以根据id取余进行拆分。
而分库分表中比较有名气的便是ShardingSphere,这里便开始介绍ShardingSphere
Apache ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
官网如下:https://shardingsphere.apache.org/document/5.1.2/cn/overview/
详细的便不多说,自己可以参考官网阅读。下面开始实战。
单库水平分表实战场景
我们主要分为2种情况
- 全新项目
- 运行中项目的整合
- Mybatis版本
- Mybatisplus版本
1、全新项目+单数据源
如果是全新项目,则可以考虑直接整合shardingjdbc即可,不需要考虑使用多数据源。因为shardingjdbc有很多不支持项,例如一些复杂函数等等。但是因为我们是重头开始开发,所以只要面对不支持项的时候,去改我们的SQL即可。但是如果是运行了一段时间的项目,就没那么容易,可能有大量的不支持项,会需要修改很多的SQL语句等。
整合步骤
1、springboot项目创建
这一步就省略了…
主要是包含以下依赖。
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test
2、整合mybatis、shardingjdbc
创建表
假设我们创建订单表,但是我们需要分表,我们以4个表为例,那么就分为 order_info_0,order_info_1,order_info_2,order_info_3
CREATE TABLE `order_info_0` ( `id` bigint NOT NULL COMMENT 'id', `name` varchar(32) DEFAULT NULL COMMENT '名称', `num` int DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `order_info_1` ( `id` bigint NOT NULL COMMENT 'id', `name` varchar(32) DEFAULT NULL COMMENT '名称', `num` int DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `order_info_2` ( `id` bigint NOT NULL COMMENT 'id', `name` varchar(32) DEFAULT NULL COMMENT '名称', `num` int DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;CREATE TABLE `order_info_3` ( `id` bigint NOT NULL COMMENT 'id', `name` varchar(32) DEFAULT NULL COMMENT '名称', `num` int DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
引入依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 com.alibaba druid-spring-boot-starter 1.2.8 mysql mysql-connector-java 8.0.19 org.projectlombok lombok org.apache.shardingsphere shardingsphere-jdbc-core-spring-boot-starter 5.1.2
yaml配置
server: port: 11001spring: autoconfigure: # 排除druid 否则报错 exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure# mybatis配置# datasource:# url: jdbc:mysql://localhost:3306/table_sharding?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai# username: root# password: 123456# driver-class-name: com.mysql.jdbc.Driver shardingsphere: # 开启sql打印 enabled: true props: # 是否显示sql sql-show: true datasource:# 数据源名称 names: sharding# 数据源实例: 如果这里还有master sharding: type: com.alibaba.druid.pool.DruidDataSource driver-class: com.mysql.cj.jdbc.Driver # 使用Druid,不能使用jdbc-url 得使用url url: jdbc:mysql://localhost:3306/table_sharding?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456# 分片规则 rules: sharding:# 对表进行分片 tables:# 逻辑表名,代表的是需要分表的名称 order_info:# 实际节点:这里代表的是 会使用sharding数据源中 order_info表 细分为0~3 4个表 actual-data-nodes: sharding.order_info_$->{0..3}# 表策略 table-strategy:# 标准表策略 standard:# 分表的列 sharding-column: id# 分片算法名称: 来源于下面的sharding-algorithms sharding-algorithm-name: alg_hash_mod key-generate-strategy: # 主键生成策略 column: id # 主键列 key-generator-name: snowflake # 策略算法名称(推荐使用雪花算法)# 主键生成规则,SNOWFLAKE 雪花算法 key-generators: snowflake: type: SNOWFLAKE# 分片算法 sharding-algorithms: alg_hash_mod:# 类型:hash取余 类似于获取一个列的数,假如是3 3%4=0 数据就会进入第0个表 type: HASH_MOD# 分片的数量,因为是4个表,所以是4 props: sharding-count: 4mybatis: # 映射文件 配置之后,mybatis会去扫描该路径下的xml文件,才会与Mapper对应起来 mapper-locations: classpath:mapper/*.xml# 别名类(实体类)所在包 type-aliases-package: com.walker.simplesharding.entity configuration:# 驼峰转换 map-underscore-to-camel-case: true3、创建测试类、测试方法
包含Entity、Mapper、Mapper.xml等
package com.walker.simplesharding.entity;import lombok.Data;import java.util.Date;/** * @Author: WalkerShen * @DATE: 2022/3/29 * @Description: **/@Datapublic class OrderInfo { private Long id; private String name; private Integer num; private Date createTime;}package com.walker.simplesharding.mapper;import com.walker.simplesharding.entity.OrderInfo;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import java.util.List;/** * @Author: WalkerShen * @DATE: 2022/3/29 * @Description: 创建mapper接口, **///使用@Mapper,注入容器@Mapperpublic interface OrderInfoMapper { List list(); void save(OrderInfo orderInfo); void deleteById(@Param("id") Long id); void updateNameById(@Param("id") Long id, @Param("name") String name); OrderInfo getById(@Param("id") Long id); List limitOrder(); List limitOrderWithOffset();}insert into order_info(`name`,num,create_time) values(#{name},#{num},#{createTime}) update order_info set name=#{name} where id=#{id} delete from order_info where id=#{id} select * from order_info select * from order_info where id=#{id} select * from order_info order by num desc limit 10 select * from order_info order by num desc limit 2,10注意:xml需要放在application.yaml配置的路径一致,否则会扫描不到方法的.
测试方法:
package com.walker.simplesharding;import cn.hutool.json.JSON;import cn.hutool.json.JSONUtil;import com.walker.simplesharding.entity.OrderInfo;import com.walker.simplesharding.mapper.OrderInfoMapper;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.ArrayList;import java.util.Date;import java.util.List;@Slf4j@SpringBootTestclass SimpleShardingApplicationTests { @Autowired private OrderInfoMapper orderInfoMapper;// 增 @Test void add() { for (int i = 0; i4、执行测试方法
如果启动的时候,报该错误,那么就是启动命令太长了。需要更改一下启动方式
之后保存即可
新增
插入1000条数据,num分别是0~999
可以看到,自动生成雪花id,然后根据id进行取余插入不同的表
删除
从order_info_3中获取一个id,然后进行删除
可以发现会先使用id进行取余,然后路由到3表中进行删除
修改
同理
查询所有数据
打印日志:会使用union all 将数据全部拼接起来
select * from order_info_0 UNION ALL select * from order_info_1 UNION ALL select * from order_info_2 UNION ALL select * from order_info_3
查询单个
跟修改、删除一样,先路由到指定的表,然后进行查询
limit+order by
可以看到日志,是每个表先执行一次,再进行排序,然后获取10条
我们可以看一下结果是不是 num= 999~990
结果是没有问题的
limit+offset+order by
相当于跳过几条数据,这个在实际业务的分页查询中是很常见的。
查询结果我们也可以看到是正确的 num为997~988
日志如下:
但是可以看到,对比我们原来的limit 2,10
shardingjdbc会首先将每个表,进行limit 0,2+10 也就是 limit 0,12,然后将这些数据查询出来之后,再进行归并排序,获取我们想要的10条数据
当然,这个在我们跳页比较少的时候,是没问题的。但是,如果我们offset是10000,是10w呢,那么一次性可能就查询出1w条数据,10w条数据。对于sql查询和内存都有很大的压力。
所以排序问题是shardingjdbc的一个需要面对的问题
那么有几种方式呢?
1、禁止跳表,根据id和limit结合去获取数据,不使用offset
2、结合ES实现搜索引擎进行处理
后续我们再进行讲解。
遇到问题
Caused by: java.lang.ClassNotFoundException: com.alibaba.druid.pool.DruidDataSource
需要导入依赖
com.alibaba druid-spring-boot-starter 1.2.8
也可以研究一下用这种方式导入,不过好像不行
--> com.alibaba--> druid--> 1.2.8-->
Failed to determine a suitable driver class
处理方式
1、添加yaml配置
spring: autoconfigure: # 排除druid 否则报错 exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
2、运行中项目
待续…
项目源码
可以关注公众号 I am Walker回复项目源码即可.