当前位置:   article > 正文

ShardingSphere系列01:Shardingjdbc实现分表(含项目实践)_shardingsphere分表

shardingsphere分表

使用目的

为什么使用需要分库分表,在数据量比较小的时候,是不需要考虑使用分库分表的,一般而言,使用单库和单表就ok了,但是使用Mysql,当数据量达到400w~500w的查询时,查询速度便慢了很多了,因此就需要分库和分表了
在《阿里开发手册嵩山版》中有写到:
image.png
而分库分表中,有垂直分片和水平分片之说,可以简单理解,垂直分片,就是将一个大表,拆分成多个小表。例如将一张大的订单表分为多个表;水平分表,就是同个表,拆分成多个一样的表进行存储。例如可以按照时间进行拆分,也可以根据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项目创建

这一步就省略了…
主要是包含以下依赖。

<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
引入依赖
 <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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
yaml配置
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

3、创建测试类、测试方法

包含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;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
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();

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
<?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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

注意:xml需要放在application.yaml配置的路径一致,否则会扫描不到方法的.
image.png
image.png

测试方法:

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));
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

4、执行测试方法

image.png

如果启动的时候,报该错误,那么就是启动命令太长了。需要更改一下启动方式
image.png
image.png
image.png
之后保存即可

新增

插入1000条数据,num分别是0~999
可以看到,自动生成雪花id,然后根据id进行取余插入不同的表
image.png

删除

从order_info_3中获取一个id,然后进行删除
可以发现会先使用id进行取余,然后路由到3表中进行删除
image.png

修改

同理
image.png

查询所有数据

打印日志:会使用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
  • 1
  • 2
  • 3
  • 4
查询单个

跟修改、删除一样,先路由到指定的表,然后进行查询
image.png

limit+order by

可以看到日志,是每个表先执行一次,再进行排序,然后获取10条
image.png
我们可以看一下结果是不是 num= 999~990
结果是没有问题的

limit+offset+order by

相当于跳过几条数据,这个在实际业务的分页查询中是很常见的。
查询结果我们也可以看到是正确的 num为997~988
日志如下:image.png
image.png
但是可以看到,对比我们原来的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

需要导入依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

也可以研究一下用这种方式导入,不过好像不行

       <dependency>-->
            <groupId>com.alibaba</groupId>-->
            <artifactId>druid</artifactId>-->
            <version>1.2.8</version>-->
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
Failed to determine a suitable driver class

处理方式
1、添加yaml配置

spring:
  autoconfigure: # 排除druid 否则报错
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  • 1
  • 2
  • 3

2、运行中项目

待续…

项目源码

可以关注公众号 I am Walker回复项目源码即可.

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号