MySQL从入门到精通【实践篇】之使用Sharding-JDBC 分库分表详解

文章目录

  • 0. 前言
    • 本文技术组件版本
    • 基本介绍
  • 2. 使用和配置:
      • 步骤1 引入依赖
      • 步骤2 配置数据源和分片策略
      • 步骤3 核心代码
        • MybatisPlusConfig 核心配置
        • OrderService
        • OrderServiceImpl
        • OrderInfo
        • OrderMapper
        • OrderController
        • BaseMapper
  • 3. 数据库分片配置
        • 在我的demo工程中大家可以看到如下的配置
      • 3.1 表的分片策略
      • 3.2 分片算法
      • 3.3. 自定义分片算法
        • 3.3.1. 自定义算法类实现
        • 3.3.2. 自定义表达式实现
  • 4. 数据路由
        • 具体的执行过程
        • 通常复杂查询Sharding-jdbc的处理方式
          • 1. 分页查询
          • 2. 聚合查询
          • 3. 子查询和关联查询
          • 4. 跨库事务
  • 5. 数据库连接池
        • 使用druid连接池
  • 6. SQL执行引擎
  • 7. 参考文档

在这里插入图片描述

0. 前言

Sharding-JDBC 是一款开源的分布式数据库中间件,主要目标是充分利用数据库的水平扩展能力,尽可能地避免改变已有的应用程序代码,简化分布式系统的开发难度。本文主要是使用sharding-jdbc 实现分库分表 以及各种分库分表策略实战验证。

在学习本文之前建议 先看下我之前写的两篇文章
《MySQL从入门到精通【进阶篇】MySQL分库分表详解》
《MySQL从入门到精通【进阶篇】MySQL的读写分离详解》

本文技术组件版本

> spring-boot 2.7.15
> jdk 1.8
> mybatis-plus-boot-starter 3.5.3.2 最新版
> mysql  8.0
> sharding-jdbc 4.0.1

基本介绍

分库分表是一种常见的数据库扩展策略,它通过将数据分散到多个数据库或表中,来提高系统的性能和可扩展性。然而,实现分库分表并不简单,它涉及到数据的路由、SQL的改写、结果的合并等一系列复杂的问题。其实我们也不需要自己造轮子,因为已经有一批优秀的开源组件满足了我们的基本需求。
最著名的组件MyCAT大家肯定若有耳闻。MyCAT 它提供了包括分库分表、读写分离、高可用等在内的一系列功能。MyCAT是基于Java开发的,可以透明地将多个MySQL数据库合并为一个逻辑数据库。而本章节我们要讲的是国内比较牛逼的ShardingSphere团队开发的Sharding-JDBC。后来加入了Apache 项目。

目前Sharding-JDBC是Apache ShardingSphere项目的一部分,它是一个在Java的JDBC层提供额外服务的轻量级框架,可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。使用Sharding-JDBC,我们可以轻松地实现MySQL的分库分表,而无需修改业务代码。

本文,我们将深入探讨如何使用Sharding-JDBC实现MySQL的分库分表。我们将从Sharding-JDBC的基本概念和原理开始,然后通过实际的示例和代码,详细介绍如何配置和使用Sharding-JDBC。无论你是菜鸟,还是有一定数据库经验的开发者,我相信你都能从这篇文章中也能get到一些有用的知识。

2. 使用和配置:

demo工程的目录结构如下
在这里插入图片描述

步骤1 引入依赖

在pom.xml中添加Sharding-JDBC的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.15</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.icepip</groupId><artifactId>springBoot-icepip-sharding-jdbc</artifactId><version>0.0.1-SNAPSHOT</version><name>springBoot-icepip-sharding-jdbc</name><description>springBoot-icepip-sharding-jdbc</description><properties><java.version>1.8</java.version><mysql.version>8.0.13</mysql.version><sharding-sphere.version>4.0.1</sharding-sphere.version><mybatis-plus-boot-starter.version>3.5.3.2</mybatis-plus-boot-starter.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version><scope>runtime</scope></dependency><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>${sharding-sphere.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus-boot-starter.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

步骤2 配置数据源和分片策略

在application.properties中配置数据源和分片规则。

server.port=8098management.health.db.enabled=falsespring.shardingsphere.datasource.names=ds0,ds1,ds2
logging.level.root=trace # 专门搞成trace 我们可以看到 SQL 解析、路由计算、SQL 改写和 SQL 执行spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://172.20.6.xx:3306/icepip_demo0?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&verifyServerCertificate=false&autoReconnct=true&autoReconnectForPools=true&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=passwordspring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://172.20.6.xx:3306/icepip_demo1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&verifyServerCertificate=false&autoReconnct=true&autoReconnectForPools=true&allowMultiQueries=true
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=passwordspring.shardingsphere.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds2.jdbc-url=jdbc:mysql://172.20.6.xx:3306/icepip_demo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&verifyServerCertificate=false&autoReconnct=true&autoReconnectForPools=true&allowMultiQueries=true
spring.shardingsphere.datasource.ds2.username=root
spring.shardingsphere.datasource.ds2.password=password# 设置分库策略为内联分片策略,分片列为user_id
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id# 设置分库策略算法表达式,根据user_id的值取模3得到分库后缀,拼接到ds前面作为数据源名 因为我上面是3个库所以与3取模,实际根据分片库的数量配置
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 3}# 设置t_order表的实际数据节点,实际数据节点由数据源名和表名组成
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..2}.t_order$->{0..1}# 设置t_order表的内联分片策略,分片列为order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id# 设置t_order表的主键生成器列为order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id# 设置t_order表的主键生成器类型为雪花算法
spring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE# 设置t_order表的内联分片策略算法表达式,根据order_id的值取模2得到分片后缀,拼接到t_order前面作为表名
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order$->{order_id % 2}# 设置默认数据源名称为ds0
spring.shardingsphere.sharding.default-data-source-name=ds0# 设置显示SQL语句
spring.shardingsphere.props.sql.show=true# 允许覆盖Bean定义
spring.main.allow-bean-definition-overriding=true# mybatis-plus
mybatis-plus.mapper-locations=classpath:/mapper/*.xml
mybatis-plus.configuration.jdbc-type-for-null='null'

步骤3 核心代码

在项目中使用JdbcTemplate或者JPA等方式进行数据库操作,Sharding-JDBC会自动进行分片。

MybatisPlusConfig 核心配置

package com.icepip.shardingjdbc.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;/*** Mybatis-plus 全局拦截器* 可通过开关选择启用不同作用的全局拦截器 {@see MybatisPlusConfig}** @author 冰点* @date 2022-06-23 2:13 PM*/
@Configuration
@Slf4j
public class MybatisPlusConfig {@Value("${icepip.mybatis-plus-ext.db-key.enable:false}")private boolean dbKeyEnable;@Value("${icepip.datasource.dynamic.primary:ds0}")private String primaryCode;@Value("${icepip.mybatis-plus-ext.db-key:ds0}")private String dbKey;@Value("${icepip.mybatis-plus-ext.pagination-interceptor.enable:true}")private boolean paginationInterceptorEnable;@Value("${icepip.mybatis-plus-ext.optimistic-Locker.enable:true}")private boolean optimisticLockerEnable;@Value("${icepip.mybatis-plus-ext.sql-attack-interceptor.enable:false}")private boolean sqlAttackInterceptorEnable;@Value("${icepip.mybatis-plus-ext.tenant-interceptor.enable:true}")private boolean tenantEnable;@Beanpublic DataSourceHealthIndicator dataSourceHealthIndicator(DataSource dataSource) {return new DataSourceHealthIndicator(dataSource, "select 1");}@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();if (optimisticLockerEnable) {interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());log.info("已开启乐观锁校验[当要更新一条记录的时候,希望这条记录没有被别人更新],被@version字段修饰的字段为乐观锁字段,此必须要有默认值或初始值");}if (sqlAttackInterceptorEnable) {interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());log.info("已开启危险SQL拦截[由于开启了直接输入sql操作数据库,防止误操作],危险SQL拦截判断规则详见@see BlockAttackInnerInterceptor");}if (tenantEnable) {// 已简化log.info("已开启多租户模式,默认项目id为租户id");}if (paginationInterceptorEnable) {PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor();innerInterceptor.setOptimizeJoin(false);interceptor.addInnerInterceptor(innerInterceptor);log.info("已开启自动分页插件,如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage,如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动入参的IPage.setRecords(返回的 List)");}return interceptor;}}

OrderService

package com.icepip.shardingjdbc.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.icepip.shardingjdbc.model.OrderInfo;import java.io.Serializable;public interface OrderService extends IService<OrderInfo> {@Overrideboolean save(OrderInfo entity);@Overrideboolean removeById(Serializable id);@Overrideboolean updateById(OrderInfo entity);IPage<OrderInfo> page(Page<?> page, OrderInfo orderInfo);}

OrderServiceImpl

package com.icepip.shardingjdbc.service;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icepip.shardingjdbc.mapper.OrderMapper;
import com.icepip.shardingjdbc.model.OrderInfo;
import org.springframework.stereotype.Service;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {@Overridepublic boolean save(OrderInfo entity) {return super.save(entity);}@Overridepublic boolean removeById(Serializable id) {return super.removeById(id);}@Overridepublic boolean updateById(OrderInfo entity) {return super.updateById(entity);}@Overridepublic IPage<OrderInfo> page(Page<?> page, OrderInfo orderInfo) {Map<String, Object> map = new HashMap<>();map.put("userId", orderInfo.getUserId());map.put("orderId", orderInfo.getOrderId());map.put("orderName", orderInfo.getOrderName());map.put("orderStatus", orderInfo.getOrderStatus());page.addOrder(new OrderItem("order_id",false));return super.baseMapper.selectAllByCondition(page, map);}
}

OrderInfo

package com.icepip.shardingjdbc.model;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("t_order")
public class OrderInfo {@TableId(value = "order_id")private Long orderId;@TableField(value = "order_name")private String orderName;@TableField(value = "order_status")private Integer orderStatus;@TableField(value = "user_id")private Long userId;}

OrderMapper

package com.icepip.shardingjdbc.mapper;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.icepip.shardingjdbc.base.BaseMapper;
import com.icepip.shardingjdbc.model.OrderInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.Map;@Mapper
public interface OrderMapper extends BaseMapper<OrderInfo> {IPage<OrderInfo> selectAllByCondition(Page<?> page, @Param("condition") Map<String, Object> condition);int deleteById(@Param("condition")Map<String, Object> condition);}

OrderController

package com.icepip.shardingjdbc.controller;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.icepip.shardingjdbc.model.OrderInfo;
import com.icepip.shardingjdbc.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/v1/order")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("add")public boolean save(@RequestBody OrderInfo orderInfo) {return orderService.save(orderInfo);}@DeleteMapping("/{orderId}")public boolean deleteById(@PathVariable("orderId")Long id) {return orderService.removeById(id);}@PutMapping("/{orderId}")public boolean updateById(@PathVariable("orderId")Long orderId,@RequestBody OrderInfo orderInfo) {orderInfo.setOrderId(orderId);return orderService.updateById(orderInfo);}@GetMapping("/page")public IPage page(@RequestParam(name = "pageNum", required = false, defaultValue = "1")Integer pageNum,@RequestParam(name = "pageSize", required = false, defaultValue = "10")Integer pageSize,@RequestParam(name = "orderId", required = false)Long orderId,@RequestParam(name = "orderStatus", required = false)Integer orderStatus,@RequestParam(name = "orderName", required = false)String orderName) {Page<OrderInfo> page = new Page<>(pageNum,pageSize);OrderInfo orderInfo = new OrderInfo();orderInfo.setOrderId(orderId);orderInfo.setOrderName(orderName);orderInfo.setOrderStatus(orderStatus);return orderService.page(page, orderInfo);}
}

BaseMapper

package com.icepip.shardingjdbc.base;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;import java.util.Map;public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> {IPage<T> selectAllByCondition(Page<?> page, @Param("condition")Map<String, Object> condition);int deleteById(@Param("condition")Map<String, Object> condition);}
<?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" >
<mapper namespace="com.icepip.shardingjdbc.mapper.OrderMapper"><sql id="Base_Column_List">order_id, order_name, order_status, user_id</sql><select id="selectAllByCondition" resultType="com.icepip.shardingjdbc.model.OrderInfo">select<include refid="Base_Column_List"/>fromt_order<where><if test="condition.userId != null">and user_id = #{condition.userId, jdbcType=BIGINT}</if><if test="condition.orderId != null">and order_id = #{condition.orderId, jdbcType=BIGINT}</if><if test="condition.orderName != null and condition.orderName != ''">and order_name like concat('%',#{condition.orderName,jdbcType=VARCHAR} ,'%')</if><if test="condition.orderStatus != null">and order_status = #{condition.orderStatus, jdbcType=INTEGER}</if></where></select><delete id="deleteById">delete from t_order where order_id = #{condition.orderId, jdbcType=BIGINT} and user_id = #{condition.userId, jdbcType=BIGINT}</delete></mapper>

3. 数据库分片配置

Sharding-JDBC 提供了强大的数据分片能力,可以根据业务规则将数据分散到多个数据库或表中,提高数据处理能力。

  • 分片策略:如根据某字段的值进行取模分片等
  • 分片算法:如内置的基于取模的分片算法、基于范围的分片算法等

Sharding-JDBC的数据分片功能使得对海量数据的管理和查询更加高效。通过将数据分散到多个数据库或表中,可以避免数据量过大导致的系统性能下降,提高系统的查询和处理速度。
例如,如果业务数据主要根据用户ID进行查询,那么可以选择用户ID作为分片键,然后根据用户ID的值进行取模操作,将数据均匀分散到不同的数据库或表中。

在我的demo工程中大家可以看到如下的配置

设置分库策略为内联分片策略,分片列为user_id.设置分库策略算法表达式,根据user_id的值取模3得到分库后缀,拼接到ds前面作为数据源名 因为我上面是3个库所以与3取模,实际根据分片库的数量配置
我是三个库。
在这里插入图片描述

在这里插入图片描述

3.1 表的分片策略

Sharding-JDBC支持各种分片策略,比如单键分片、复合键分片等。开发者可以根据业务需求选择合适的分片策略。

在这里插入图片描述
这是对Apache ShardingSphere的配置进行解析:

  1. spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds$->{0..2}.t_order$->{0..1}
    设置数据分片的逻辑表和实际数据节点。逻辑表是t_order,实际数据节点是ds0.t_order0、ds0.t_order1、ds1.t_order0、ds1.t_order1、ds2.t_order0、ds2.t_order1,其中ds表示数据源。

  2. spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
    设置分片策略的列,这里order_id列作为分片依据。

  3. spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_idspring.shardingsphere.sharding.tables.t_order.key-generator.type=SNOWFLAKE
    设置主键生成策略,这里使用的是雪花算法(Snowflake)生成order_id字段的主键。

  4. spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order$->{order_id % 2}
    设置分片策略的算法表达式,这里通过order_id的值取模2的结果作为分片后缀,拼接到t_order前面作为表名。

3.2 分片算法

其实Sharding-JDBC内置了多种分片算法,比如基于取模的分片算法、基于范围的分片算法、基于哈希的分片算法等。取模分片算法是一种简单而常用的分片算法,适用于数据量大并且分布比较均匀的场景;范围分片算法适用于数据在分片键上的分布不均匀的场景,可以通过指定分片键的范围来进行分片;哈希分片算法则适用于数据量特别大的场景,通过对分片键进行哈希运算,可以将数据均匀地分散到各个数据库或表中。
ShardingSphere提供了多种内置的分片策略,

  1. 标准分片策略(StandardShardingStrategy):适用于分片键的值具有明确的分片逻辑,例如:偶数分到一个分片,奇数分到另一个分片。

  2. 复合分片策略(ComplexShardingStrategy):适用于多个分片键之间存在复杂关系的场景。

  3. Hint分片策略(HintShardingStrategy):适用于分片键的值无法从SQL中直接获取,需要应用通过Hint API提供。

  4. 类型分片策略(ClassBasedShardingStrategy):适用于分片键的值需要通过自定义算法进行分片。

  5. 范围分片策略(RangeShardingStrategy):适用于分片键的值具有范围关系的场景。

对我们上面的需求 也可以使用范围分片策略。 我们定义了一个名为range_order_id的范围分片算法,它将order_id字段的值除以1024,然后将结果用于确定分片。

sharding.jdbc.config.sharding.tables.t_order.table-strategy.range.sharding-column=order_id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.range.sharding-algorithm-name=range_order_id
sharding.jdbc.config.sharding.sharding-algorithms.range_order_id.type=RANGE
sharding.jdbc.config.sharding.sharding-algorithms.range_order_id.props.algorithm-expression=ds${order_id / 1024}

3.3. 自定义分片算法

3.3.1. 自定义算法类实现

假设我们想根据订单的创建时间进行分片。我们可以选择按照年份进行分片,即每年的数据存储在一个分片中。这样,我们可以创建一个自定义的分片算法, 其实也可以用表达式处理。

package com.icepip.shardingjdbc.config;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;import java.util.Calendar;
import java.util.Collection;
import java.util.Date;/***  自定义分片策略* @author 冰点* @version 1.0.0* @date 2023/9/5 14:52*/public class OrderTableShardingAlgorithm implements PreciseShardingAlgorithm<Date> {@Overridepublic String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {Date orderCreateTime = shardingValue.getValue();int year = getYear(orderCreateTime);for (String each : availableTargetNames) {if (each.endsWith(year + "")) {return each;}}throw new IllegalArgumentException();}private int getYear(Date date) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);return calendar.get(Calendar.YEAR);}
}
sharding:tables:t_order:actual-data-nodes: ds${0..1}.t_order_${0..9}table-strategy:standard:sharding-column: create_timeprecise-algorithm-class-name: com.yourpackage.OrderTableShardingAlgorithm 

t_order是逻辑表名,ds${0..1}.t_order_${0..9}表示实际的数据节点,create_time是分片列,com.yourpackage.OrderTableShardingAlgorithm是你自定义的分片算法类。

3.3.2. 自定义表达式实现

如果我们不使用自定义算法类 也是可以实现的。需要配置表达式去解析。这种方式需要数据库支持在SQL中直接使用Java的日期时间函数,否则可能会出现错误。如果数据库不支持,那么还是需要写自定义的分片策略。

我们首先获取订单的创建时间,然后提取出年份。然后,我们遍历所有的可用的目标表名,如果表名的后缀与年份匹配,那么我们就选择这个表作为目标表。

 
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds${0..1}.t_order_${2018..2023}
sharding.jdbc.config.sharding.tables.t_order.table-strategy.standard.sharding-column=create_time
sharding.jdbc.config.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=orderTableShardingAlgorithm
sharding.jdbc.config.sharding.sharding-algorithms.orderTableShardingAlgorithm.type=INLINE
sharding.jdbc.config.sharding.sharding-algorithms.orderTableShardingAlgorithm.props.algorithm-expression=t_order_${create_time.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().getYear()}

在这里插入图片描述

sharding.jdbc.config.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name定义了分片算法的名称。
sharding.jdbc.config.sharding.sharding-algorithms.orderTableShardingAlgorithm.type定义了分片算法的类型,sharding.jdbc.config.sharding.sharding-algorithms.orderTableShardingAlgorithm.props.algorithm-expression定义了分片的表达式。
当你插入一个新的订单时,ShardingSphere会根据订单的创建时间来选择一个分片进行存储。

4. 数据路由

Sharding-JDBC 在处理业务请求时,能够根据分片规则自动路由到对应的数据库或表,无需开发者关注底层数据存储细节。
数据路由的过程主要分为四个步骤:SQL 解析、路由计算、SQL 改写和 SQL 执行。

  1. SQL 解析:Sharding-JDBC 首先对 SQL 语句进行解析,得到抽象语法树(AST),并提取出路由条件。

  2. 路由计算:通过使用分片键和分片算法,结合解析出的路由条件,计算出 SQL 语句需要访问的目标数据库和表。

  3. SQL 改写:根据路由结果,对原始 SQL 语句进行改写,将全局表和分片表替换为真实的物理表。

  4. SQL 执行:最后,将改写后的 SQL 语句发送到目标数据库执行。

这四个步骤完全由 Sharding-JDBC 自动完成,开发者只需配置好分片规则,即可做到数据的水平切分和分布式访问,大大简化了开发工作。

我们可以通过日志 看到SQL改写。我故意加了一些order by 排序想看下
sharding-jdbc改写后的结果,发现它是在每个库和每个表都执行了此sql 然后在内存中在做排序。

在这里插入图片描述

具体的执行过程

  1. 首先,Sharding-JDBC会解析SQL,提取出表名、列名、条件、排序字段等关键信息。

  2. Sharding-JDBC会进行SQL路由。根据分片策略,它会计算出SQL语句需要执行的具体的数据库和数据表。

  3. 然后,Sharding-JDBC会对原始的SQL语句进行改写。这一步中,它会将逻辑表名替换为实际的物理表名,并根据分片键将查询条件拆分为多个子查询。同时,它还会在每个子查询中添加相应的排序和分页条件。

  4. 并行查询:改写后的SQL语句会被并行发送到对应的数据库执行。每个数据库会返回一个结果集。

  5. 结果合并:在内存中,Sharding-JDBC会将来自不同数据库的结果集进行合并。首先,它会按照排序字段对所有结果进行排序。然后,它会根据分页条件,取出需要的部分数据返回给客户端。

例如,原始的SQL

SELECT * FROM t_order ORDER BY order_id DESC LIMIT 10 OFFSET 20

如果t_order表按照order_id字段分片,存储在两个数据库的t_order_0t_order_1两个表中,那么改写后的SQL可能会是:

SELECT * FROM t_order_0 ORDER BY order_id DESC LIMIT 30
SELECT * FROM t_order_1 ORDER BY order_id DESC LIMIT 30

这两条SQL会并行在各自的数据库中执行,然后Sharding-JDBC会将两个结果集合并,按照order_id进行排序,取出第21到30条记录返回给客户端。

大家有没有思考过在上述分库分表的例子中,我们的原始查询是希望取出排序后的第21到30条记录,也就是希望取出10条记录,但在改写后的SQL中我们却对每个子查询使用了LIMIT 30,这是为什么呢?

这是因为在分库分表的情况下,数据是分散在不同的数据库和表中的。当我们需要对数据进行排序并取出一部分数据时,我们不能保证每个子查询都能贡献出足够的数据。

比如在我们的例子中,我们希望取出排序后的第21到30条记录。如果我们直接对每个子查询使用LIMIT 10 OFFSET 20,那么有可能某个子查询返回的数据不足10条(比如只返回了5条),这样合并后的结果集就不足10条了。为了避免这种情况,我们需要让每个子查询返回更多的数据,比如返回前30条数据,这样即使某个子查询的数据不足,我们也有可能从其他子查询中获取到足够的数据。

我只是做了一个示例,这只是一种简化的做法,实际上Sharding-JDBC在处理这种情况时会更加复杂和智能。

通常复杂查询Sharding-jdbc的处理方式

1. 分页查询

如上面我们举的例子。在分库分表的环境中,处理分页查询和排序查询会相对比较复杂。因为数据分散在不同的库和表中,我们不能直接使用LIMITOFFSET关键词进行分页。Sharding-JDBC更新的方法是对每个子查询使用改写后的LIMITOFFSET值,然后在内存中对所有的结果进行合并和排序。

2. 聚合查询

类似的,对于COUNT()SUM()AVG()等聚合函数,Sharding-JDBC也会对每个子查询进行聚合操作,然后在内存中对所有的结果进行二次聚合。比如,对于COUNT()函数,Sharding-JDBC会将各个子查询的结果加起来;对于AVG()函数,Sharding-JDBC会将所有子查询的SUM()结果相加,然后除以所有子查询的COUNT()结果相加得到的数。

3. 子查询和关联查询

如果SQL中涉及到子查询或者关联查询,比如JOININ等,相对来说也是很复杂的。但是Sharding-JDBC也能正确地处理。它会将包含子查询或关联查询的复杂SQL,逐步拆解成单表查询,然后逐一进行执行,最后在内存中对所有的结果进行合并。 我们举一个简单的实力 理解一下。
假设我们在一个电商系统中有两张分表,users(用户表)和orders(订单表),并且它们根据用户ID(user_id)进行了分片。

例如,下面是一个包含子查询的SQL语句。我们的需求是查询希望找到所有订单总金额大于1000的用户。 Sharding-JDBC会按以下方式处理:

原始SQL

SELECT * FROM users WHERE user_id IN (SELECT user_id FROM orders WHERE total_amount > 1000);
  1. Sharding-JDBC会解析这个SQL,找到主查询和子查询,然后分别进行路由。因为orders表按照user_id进行了分片,所以子查询可能要在多个表中进行。主查询中的users表也是根据user_id分片的,因此路由结果可能会涉及多个表。

  2. 对于主查询和子查询,Sharding-JDBC会逐一改写并执行。 对于子查询,Sharding-JDBC会在每个orders表中执行该查询,找到所有订单总金额大于1000的用户。
    得到的user_id将用于主查询。在每个改写过的users表中执行主查询,找出对应的用户记录。

  3. 结果合并 Sharding-JDBC会将来自不同表的查询结果进行合并,组成一个统一的结果集返回。

所以我们再开发过程尽量避免子查询和join 查询。

4. 跨库事务

在分库分表的环境中,实现横跨多个数据库的事务是一件挑战性的工作。通常的做法是借助某种分布式事务协议,如XA或者TCC。Sharding-JDBC通过整合开源分布式事务框架Seata,提供了对XA事务的支持。这个我们后续再专门来讲一下,如何集成实现。

5. 数据库连接池

Sharding-JDBC 支持多种流行的数据库连接池,如 HikariCP、Druid 等。可以对每个物理数据库设置独立的连接池。
如果你想使用Druid作为连接池,你可以按照以下方式进行配置:
我的示例里面使用的是hikari连接池 ,此处不做详解,下面是使用Druid 的示例。

使用druid连接池

spring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds0.url=jdbc:mysql://localhost:3306/ds0
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.ds1.url=jdbc:mysql://localhost:3306/ds1
spring.shardingsphere.datasource.ds1.username=root
spring.shardingsphere.datasource.ds1.password=123456

在项目中使用了Spring Boot的starter for Druid(spring-boot-starter-data-druid),也可以额外配置Druid的一些属性,例如初始连接数、最小空闲连接数、最大活动连接数等。这些配置可以通过spring.shardingsphere.datasource.ds0.druid.*进行。

6. SQL执行引擎

Sharding-JDBC 对 SQL 语句进行解析、改写和执行,能够支持绝大部分的 SQL 语法,包括对联表查询、子查询、分页查询等复杂 SQL 的支持。这个我们已经在数据路由中已经详细讲解,此处就不做详解,大家只需要知道Sharding-jdbc的组成核心部分有SQL执行引擎,以及他的主要作用。

7. 参考文档

本文参考了以下文档

  1. Sharding-JDBC GitHub:https://github.com/apache/shardingsphere

  2. Sharding-JDBC官方文档包括了Sharding-JDBC的安装、配置和使用方法,以及一些高级话题,比如分片策略、SQL支持等。

  3. Sharding-JDBC API文档 对于开发者来说是一个非常有用的参考资源。

  4. Sharding-JDBC示例项目:https://github.com/apache/shardingsphere-example

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/122141.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高等数学刷题

研究可不可导的两大问题 1.▲x能不能->0(正负均要) 2.函数可不可以不连续&#xff0c;通常用第二个例子 隐藏条件的挖掘 关键在于分析出f(x0)0f(0)

Kubernetes技术--k8s核心技术 configMap

1.概述 configMap最主要的作用是存储一些不加密的数据到/etcd,让pod以变量或者数据卷(volume)挂载到容器。 应用场景:配置文件、存储信息等 2.使用 -1.创建配置文件。 这里我们需要先编写一个配置文件。使用redis,如下所示:

postgresql-类型转换函数

postgresql-类型转换函数 简介CAST 函数to_date函数to_timestampto_charto_number隐式类型转换 简介 类型转换函数用于将数据从一种类型转换为另一种类型。 CAST 函数 CAST ( expr AS data_type )函数用于将 expr 转换为 data_type 数据类型&#xff1b;PostgreSQL 类型转 换…

axios返回几种数据格式? 其中Blob返回时的size是什么意思?

axios返回几种数据格式? 其中Blob返回时的size是什么意思&#xff1f; 1、字符串&#xff08;String&#xff09;&#xff1a;服务器可以返回纯文本或HTML内容&#xff0c;Axios会将其作为字符串返回。 2、JSON&#xff08;JavaScript Object Notation&#xff09;&#xff…

Matlab信号处理1:模拟去除信号噪声

由于工作内容涉及信号系统、信号处理相关知识&#xff0c;本人本硕均为计算机相关专业&#xff0c;专业、研究方向均未涉及信号相关知识&#xff0c;因此需进行系统地学习。之前已将《信号与系统》快速过了一遍&#xff0c;但感觉较抽象且理解较浅显。在此系统地学习如何使用Ma…

RK3568平台开发系列讲解(音视频篇)H264 的编码结构

🚀返回专栏总目录 文章目录 一、H264 的编码结构1.1、帧类型1.2、GOP1.3、Slice沉淀、分享、成长,让自己和他人都能有所收获!😄 📢视频编码的码流结构其实就是指视频经过编码之后得到的二进制数据是怎么组织的,换句话说,就是编码后的码流我们怎么将一帧帧编码后的图像…

vue优化首屏加载时间优化-cdn引入第三方包

前言 为什么要进行首屏加载优化&#xff0c;因为随着我们静态资源和第三方包和代码增加&#xff0c;压缩之后包会越来越大 随着网络的影响&#xff0c;在我们第一输入url请求资源时候&#xff0c;网络阻塞&#xff0c;加载时间长&#xff0c;用户体验不好 仔细观察后就会发现…

肖sir__设计测试用例方法之判定表06_(黑盒测试)

设计测试用例方法之判定表 1、判定表&#xff1a;是一种表达逻辑判断的工具。 2、判定表&#xff1a;包含四部分 1&#xff09;条件桩&#xff08;condition stub&#xff09;:列出问题的 所有条件&#xff08;通常条件次序无关紧要&#xff09;。 2&#xff09;条件项&#x…

zabbix监控实战

1 zabbix监控平台部署 更改的密码为HLThlt123 创建zabbix数据库 配置zabbix server 配置zabbix前端 启动服务 访问 更改成中文 修改字体文件 2 zabbix添加监控节点 在客户端 在server端 手动添加 自动添加 03 zabbix api 自动注册 停掉自动发现 删掉server3 自动注册 3 zab…

Springboot - 13.spring-boot-starter-security集成

&#x1f440;Spring Boot Starter Security 中文文档 Spring Security中文文档 &#x1f440;Spring Boot Starter Security 运行流程 当然可以。首先&#xff0c;我们会将用户存储和认证的流程融入整个Spring Boot Starter Security的使用流程中。以下是当你使用Spring Bo…

01_TMS320F28004x系列MCU介绍和资料搜集

1. TI C2000 实时微控制器 TI公司在处理器方面的产品线有&#xff1a;基于ARM内核的微控制器/微处理器、MSP430微控制器、C2000系列实时微控制器、还有数字信号处理器&#xff08;DSP&#xff09;。 其中&#xff0c;C2000是TI公司专门针对实时控制推出的32位微控制器。TI公司…

Java复习-20-接口(1)

接口的定义及使用 如果相对外部隐藏全部的实现细节&#xff0c;就要通过接口来实现。 接口的定义 使用interface关键字来定义。由于接口描述的是一个公共的定义标准&#xff0c;所以在接口之中所有的抽象方法的访问权限都为public interface IMessage{ // 为了区分接口&…

Cyber RT学习笔记 --- 1.Cyber RT框架介绍

1.Cyber RT框架介绍 1.1 Cyber RT简介 Apollo Cyber是首个专为自动驾驶定制的高性能且开源的实时通信框架&#xff0c;于2019年与Apollo 3.5开放平台同期发布&#xff0c;它主要解决了自动驾驶系统的高并发、低延迟、高吞吐、任务调度等问题&#xff0c;同时还提供了多种通信…

Gof23设计模式之策略模式

1.概述 该模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式&#xff0c;它通过对算法进行封装&#xff0c;把使用算法的责任和算法的实现分割开来&#xff0c;…

Xilinx UltraScale架构之可配置逻辑块CLB

目录 一、概览 二、UltraScale架构 2.1 UltraScale/UltraScale特点 2.2 与7系列CLB差异 三、 CLB结构 3.1 LUT 3.2 FF 3.3 多路选择器Multiplexers 3.4 进位链Carry Chain 四、应用 4.1 分布式RAM 4.2 移位寄存器 4.3 进位链Carry Chain 五、参考资料 一、概览 二…

Linux--进程概念

1.什么是程序&#xff1f;什么是进程&#xff1f;有什么区别&#xff1f; 程序&#xff1a;是静态的概念&#xff0c;gcc xxx.c -o pro 磁盘中生成的pro文件&#xff0c;叫做程序。 进程&#xff1a;是程序的一种与运行活动&#xff0c;通俗的意思是程序跑起来了&#xff0c;系…

若依tab-content面板失效、使用load的解决方法(附详细步骤)

【版权所有&#xff0c;文章允许转载&#xff0c;但须以链接方式注明源地址&#xff0c;否则追究法律责任】【创作不易&#xff0c;点个赞就是对我最大的支持】 前言 仅作为学习笔记&#xff0c;供大家参考 总结的不错的话&#xff0c;记得点赞收藏关注哦&#xff01; 思路&…

2023.09.03 学习周报

文章目录 摘要文献链接题目亮点本文工作 题目亮点本文工作 题目亮点本文工作 大气污染物传输的相关内容总结 摘要 本周阅读了三篇论文&#xff0c;第一篇文章的核心为改进PageRank算法和标签传播算法实现大气污染物传输分析模型&#xff0c;第二篇文章的核心为将SOD、VGG和LST…

备战9月9日C/C++青少年等级考试(1~8级)

由中国电子学会举办的《全国青少年软件编程等级考试》将于9月9日&#xff08;周六&#xff09;举行&#xff0c;你准备的怎么样了&#xff1f;我在这里列举了1~8级的历届真题及解析&#xff0c;希望能助你考试通过&#xff01;&#xff01;&#xff01; C/C编程一级 一级标准 …

Mock 基本使用

mock解决的问题 开发时&#xff0c;后端还没完成数据输出&#xff0c;前端只好写静态模拟数据。数据太长了&#xff0c;将数据写在js文件里&#xff0c;完成后挨个改url。某些逻辑复杂的代码&#xff0c;加入或去除模拟数据时得小心翼翼。想要尽可能还原真实的数据&#xff0c…