赞
踩
为什么使用需要分库分表,在数据量比较小的时候,是不需要考虑使用分库分表的,一般而言,使用单库和单表就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种情况
如果是全新项目,则可以考虑直接整合shardingjdbc即可,不需要考虑使用多数据源。因为shardingjdbc有很多不支持项,例如一些复杂函数等等。但是因为我们是重头开始开发,所以只要面对不支持项的时候,去改我们的SQL即可。但是如果是运行了一段时间的项目,就没那么容易,可能有大量的不支持项,会需要修改很多的SQL语句等。
这一步就省略了…
主要是包含以下依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
假设我们创建订单表,但是我们需要分表,我们以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;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Starter for MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 需要引入druid ,否则会报错:Caused by: java.lang.ClassNotFoundException: com.alibaba.druid.pool.DruidDataSource-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid</artifactId>-->
<!-- <version>1.2.8</version>-->
<!-- </dependency>-->
<!-- Database Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.2</version>
</dependency>
server:
port: 11001
spring:
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: 4
mybatis:
# 映射文件 配置之后,mybatis会去扫描该路径下的xml文件,才会与Mapper对应起来
mapper-locations: classpath:mapper/*.xml
# 别名类(实体类)所在包
type-aliases-package: com.walker.simplesharding.entity
configuration:
# 驼峰转换
map-underscore-to-camel-case: true
包含Entity、Mapper、Mapper.xml等
package com.walker.simplesharding.entity;
import lombok.Data;
import java.util.Date;
/**
* @Author: WalkerShen
* @DATE: 2022/3/29
* @Description:
**/
@Data
public 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,注入容器
@Mapper
public interface OrderInfoMapper {
List<OrderInfo> 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<OrderInfo> limitOrder();
List<OrderInfo> limitOrderWithOffset();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:命名空间,用来映射对应的mapper
相当于将mapper和mapper.xml连接起来,这一步很重要-->
<mapper namespace="com.walker.simplesharding.mapper.OrderInfoMapper">
<insert id="save">
insert into order_info(`name`,num,create_time) values(#{name},#{num},#{createTime})
</insert>
<update id="updateNameById">
update order_info set name=#{name}
where id=#{id}
</update>
<delete id="deleteById">
delete from order_info where id=#{id}
</delete>
<select id="list" resultType="com.walker.simplesharding.entity.OrderInfo">
select * from order_info
</select>
<select id="getById" resultType="com.walker.simplesharding.entity.OrderInfo">
select * from order_info
where id=#{id}
</select>
<select id="limitOrder" resultType="com.walker.simplesharding.entity.OrderInfo">
select * from order_info
order by num desc
limit 10
</select>
<select id="limitOrderWithOffset" resultType="com.walker.simplesharding.entity.OrderInfo">
select * from order_info
order by num desc
limit 2,10
</select>
</mapper>
注意: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
@SpringBootTest
class SimpleShardingApplicationTests {
@Autowired
private OrderInfoMapper orderInfoMapper;
// 增
@Test
void add() {
for (int i = 0; i < 1000; i++) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setName("name"+i);
orderInfo.setCreateTime(new Date());
orderInfo.setNum(i);
orderInfoMapper.save(orderInfo);
}
}
// 删
@Test
void delete() {
Long id=980838688239910912L;
orderInfoMapper.deleteById(id);
}
// 改
@Test
void update() {
Long id=980838688269271040L;
String name="hello";
orderInfoMapper.updateNameById(id,name);
}
// 查 列表
@Test
void list() {
List<OrderInfo> list = orderInfoMapper.list();
System.out.println(list);
}
// 查:单个查询
@Test
void getById() {
Long id=980838688269271040L;
OrderInfo info=orderInfoMapper.getById(id);
System.out.println(info);
}
@Test
void limitOrder() {
// 整合pageHelper
List<OrderInfo> list = orderInfoMapper.limitOrder();
log.info("返回结果:{}", JSONUtil.toJsonStr(list));
}
// 分页、排序、跳过数据
@Test
void limitOrderWithOffset() {
// 整合pageHelper
List<OrderInfo> list = orderInfoMapper.limitOrderWithOffset();
log.info("返回结果:{}", JSONUtil.toJsonStr(list));
}
}
如果启动的时候,报该错误,那么就是启动命令太长了。需要更改一下启动方式
之后保存即可
插入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
跟修改、删除一样,先路由到指定的表,然后进行查询
可以看到日志,是每个表先执行一次,再进行排序,然后获取10条
我们可以看一下结果是不是 num= 999~990
结果是没有问题的
相当于跳过几条数据,这个在实际业务的分页查询中是很常见的。
查询结果我们也可以看到是正确的 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实现搜索引擎进行处理
后续我们再进行讲解。
需要导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
也可以研究一下用这种方式导入,不过好像不行
<dependency>-->
<groupId>com.alibaba</groupId>-->
<artifactId>druid</artifactId>-->
<version>1.2.8</version>-->
</dependency>
处理方式
1、添加yaml配置
spring:
autoconfigure: # 排除druid 否则报错
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
待续…
可以关注公众号 I am Walker
回复项目源码
即可.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。