Sharding-Sphere分库分表

为解决关系型数据库面对海量数据由于数据量过大而导致的性能问题,将数据进行分片是行之有效的解决方案,而将集中于单一节点的数据拆分并分别存储到多个数据库或表,称为分库分表。作为分布式数据库中间件,目标是透明化分库分表所带来的影响,让使用方尽量像使用一个数据库一样使用水平拆分之后的数据库。

另外一篇有价值文章:Springboot,Mybatis-Plus,shardingjdbc实现单列分库,多租户整合[1]

一、 业务场景

我们会使用 Sharding-JDBC 实现分库分表的功能。我们会将orders订单表,拆分到 2 个库,每个库 4 个订单表,一共 8 个表。库表的情况如下:

orders_0 库
  ├── orders_0
  └── orders_2
  └── orders_4
  └── orders_6
orders_1 库
  ├── orders_1
  └── orders_3
  └── orders_5
  └── orders_7

偶数后缀的表,在 orders_0 库下。
奇数后缀的表,在 orders_1 库下。
我们使用订单表上的 user_id 用户编号,进行分库分表的规则:

首先,按照 index = user_id % 2 计算,将记录路由到orders_${index}库。
然后,按照 index = user_id % 8 计算,将记录路由到 orders_${index}表。
库、表对应关系:

编号
1 orders_1 orders_1
2 orders_0 orders_2
3 orders_1 orders_3
4 orders_0 orders_4
5 orders_1 orders_5
6 orders_0 orders_6
7 orders_1 orders_7
8 orders_0 orders_8

考虑到部分表不需要分库分表,例如说 order_config 订单配置表,所以我们会配置路由到 orders_0 库下。

二、引入依赖

在 [pom.xml] 文件中,引入相关依赖。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sharding-datasource-table</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency> <!-- 本示例,我们使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 MyBatis 的自动化配置 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- 实现对 Sharding-JDBC 的自动化配置 -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC2</version>
        </dependency>

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

三、Application

创建 [Application.java] 类,代码如下:

package cn.iocoder.springboot.lab18.shardingdatasource;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = "cn.iocoder.springboot.lab18.shardingdatasource.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

四、应用配置文件

在 [resources]目录下,创建 [application.yaml] 配置文件。配置如下:

spring:
  # ShardingSphere 配置项
  shardingsphere:
    datasource:
      # 所有数据源的名字
      names: ds-orders-0, ds-orders-1
      # 订单 orders 数据源配置 00
      ds-orders-0:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://101.133.227.13:3306/orders_0?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: IA8oD
      # 订单 orders 数据源配置 01
      ds-orders-1:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: IA8oD
    # 分片规则
    sharding:
      tables:
        # orders 表配置
        orders:
          #actualDataNodes: ds-orders-$->{0..1}.orders_$->{0..4} # 映射到 ds-orders 数据源的 orders 表
          #actualDataNodes: ds-orders-0.orders_0, ds-orders-0.orders_2, ds-orders-0.orders_4, ds-orders-0.orders_6, ds-orders-1.orders_1, ds-orders-1.orders_3, ds-orders-1.orders_5, ds-orders-1.orders_7
          actualDataNodes: ds-orders-0.orders_$->{[0,2,4,6]}, ds-orders-1.orders_$->{[1,3,5,7]} # 映射到 ds-orders-0 和 ds-orders-1 数据源的 orders 表们
          key-generator: # 主键生成策略
            column: id
            type: SNOWFLAKE
          database-strategy:
            inline:
              algorithm-expression: ds-orders-$->{user_id % 2}
              sharding-column: user_id
          table-strategy:
            inline:
              algorithm-expression: orders_$->{user_id % 8}
              sharding-column: user_id
        # order_config 表配置
        order_config:
          actualDataNodes: ds-orders-0.order_config # 仅映射到 ds-orders-0 数据源的 order_config 表
    # 拓展属性配置
    props:
      sql:
        show: true # 打印 SQL

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: cn.iocoder.springboot.lab18.shardingdatasource.dataobject # 配置数据库实体包路径
  • mybatis 配置项下,设置 mybatis-spring-boot-starter 自动化配置 MyBatis 需要的参数。
  • spring.shardingsphere 配置项下,设置 sharding-jdbc-spring-boot-starter 自动化配置 Sharding-JDBC 需要的参数。比较复杂,我们一个一个来看。

spring.shardingsphere.datasource 配置项,我们配置了 ds-orders-0ds-orders-1 两个数据源,分别对应 orders_0orders_1 两个数据库。

spring.shardingsphere.sharding 配置项,我们配置了 ordersorder_config 逻辑表

逻辑表 :水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0t_order_9 ,他们的逻辑表名为 t_order

真实表 :在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0t_order_9

数据节点 :数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0

  • orders 配置项,设置 orders 逻辑表,使用分库分表的规则。
    • actualDataNodes :对应的数据节点,使用的是行表达式 。这里的意思是,ds-orders-0.orders_0, ds-orders-0.orders_2, ds-orders-0.orders_4, ds-orders-0.orders_6, ds-orders-1.orders_1, ds-orders-1.orders_3, ds-orders-1.orders_5, ds-orders-1.orders_7
    • key-generator :主键生成策略。这里采用分布式主键 SNOWFLAKE 方案。更多可以看 《 ShardingSphere > 概念 & 功能 > 数据分片 > 其他功能 > 分布式主键》 文档。
    • database-strategy :按照 index = user_id % 2 分库,路由到 ds-orders-${index} 数据源(库)。
    • table-strategyindex = user_id % 8 分表,路由到 orders_${index} 数据表。
  • order_config 配置项,设置 order_config 逻辑表,不使用分库分表。
    • actualDataNodes :对应的数据节点,只对应数据源(库)为 ds-orders-0order_config 表。

spring.shardingsphere.props 配置项,设置拓展属性配置。

  • sql.show :设置打印 SQL 。因为我们编写的 SQL 会被 Sharding-JDBC 进行处理,实际执行的可能不是我们编写的,通过打印,方便我们观察和理解。

五、创建数据库和表

创建数据库:

CREATE DATABASE `orders_0` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
CREATE DATABASE `orders_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

在 orders_0 数据库下,创建 orders_0、orders_2、orders_4、orders_6 数据表,order_config表。SQL 如下:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for orders_0
-- ----------------------------
DROP TABLE IF EXISTS `orders_0`;
CREATE TABLE `orders_0` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_2
-- ----------------------------
DROP TABLE IF EXISTS `orders_2`;
CREATE TABLE `orders_2` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_4
-- ----------------------------
DROP TABLE IF EXISTS `orders_4`;
CREATE TABLE `orders_4` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_6
-- ----------------------------
DROP TABLE IF EXISTS `orders_6`;
CREATE TABLE `orders_6` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';


-- ----------------------------
-- Table structure for order_config
-- ----------------------------
DROP TABLE IF EXISTS `order_config`;
CREATE TABLE `order_config` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `pay_timeout` int(11) DEFAULT NULL COMMENT '支付超时时间;单位:分钟',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

SET FOREIGN_KEY_CHECKS = 1;

在 orders_1 数据库下,创建 orders_1、orders_3、orders_5、orders_7 数据表。SQL 如下:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for orders_1
-- ----------------------------
DROP TABLE IF EXISTS `orders_1`;
CREATE TABLE `orders_1` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_3
-- ----------------------------
DROP TABLE IF EXISTS `orders_3`;
CREATE TABLE `orders_3` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_5
-- ----------------------------
DROP TABLE IF EXISTS `orders_5`;
CREATE TABLE `orders_5` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

-- ----------------------------
-- Table structure for orders_7
-- ----------------------------
DROP TABLE IF EXISTS `orders_7`;
CREATE TABLE `orders_7` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
  `user_id` int(16) DEFAULT NULL COMMENT '用户编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

SET FOREIGN_KEY_CHECKS = 1;

六、 其他操作数据库代码

其他代码见Git。

七、简单测试

7.1OrderConfigMapperTest

创建 [OrderConfigMapperTest]测试类,我们来测试一下简单的 OrderConfigMapper 的每个操作。代码如下:

package cn.iocoder.springboot.lab18.shardingdatasource.mapper;

import cn.iocoder.springboot.lab18.shardingdatasource.Application;
import cn.iocoder.springboot.lab18.shardingdatasource.dataobject.OrderDO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class OrderMapperTest {

   @Autowired
   private OrderMapper orderMapper;

   @Test
   public void testSelectById() {
       OrderDO order = orderMapper.selectById(1);
       System.out.println(order);
   }

   @Test
   public void testSelectListByUserId() {
       List<OrderDO> orders = orderMapper.selectListByUserId(1);
       System.out.println(orders.size());
   }

   @Test
   public void testInsert() {
       OrderDO order = new OrderDO();
       order.setUserId(1);
       orderMapper.insert(order);
   }

}

#testSelectById() 测试方法

执行日志如下:

// Logic SQL
2020-07-01 21:41:46.428  INFO 16652 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT
             
        id, pay_timeout
     
        FROM order_config
        WHERE id = ?
2020-07-01 21:41:46.428  INFO 16652 --- [           main] ShardingSphere-SQL                       : SQLStatement: ShardingSelectOptimizedStatement(tables=Tables(tables=[Table(name=order_config, alias=Optional.absent())], schema=Optional.absent()), groupBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.groupby.GroupBy@69f2cb04, orderBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.orderby.OrderBy@13803a94, selectItems=SelectItems(startIndex=29, stopIndex=43, distinctRow=false, items=[ColumnSelectItem(owner=null, name=id, alias=Optional.absent()), ColumnSelectItem(owner=null, name=pay_timeout, alias=Optional.absent())], tables=[TableSegment(startIndex=64, stopIndex=75, name=order_config, quoteCharacter=NONE, owner=Optional.absent(), alias=Optional.absent())]), pagination=org.apache.shardingsphere.core.optimize.sharding.segment.select.pagination.Pagination@b4732dc, containsSubquery=false)
// Actual SQL
2020-07-01 21:41:46.428  INFO 16652 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-0 ::: SELECT
             
        id, pay_timeout
     
        FROM order_config
        WHERE id = ? ::: [1]
null

Logic SQL :逻辑 SQL 日志,就是我们编写的。
Actual SQL :物理 SQL 日志,实际 Sharding-JDBC 向数据库真正发起的日志。
在这里,我们可以看到 ds-orders-0 ,表明该物理 SQL ,是路由到 ds-orders-0 数据源执行。
同时,查询的是 order_config 表。
符合我们配置的 order_config 逻辑表,不使用分库分表,对应的数据节点仅有 ds-orders-0.order_config 。

7.2 OrderMapperTest

创建 [OrderMapperTest] 测试类,我们来测试一下简单的 OrderMapper 的每个操作。代码如下:

package cn.iocoder.springboot.lab18.shardingdatasource.mapper;

import cn.iocoder.springboot.lab18.shardingdatasource.Application;
import cn.iocoder.springboot.lab18.shardingdatasource.dataobject.OrderDO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class OrderMapperTest {

    @Autowired
    private OrderMapper orderMapper;

    @Test
    public void testSelectById() {
        OrderDO order = orderMapper.selectById(1);
        System.out.println(order);
    }

    @Test
    public void testSelectListByUserId() {
        List<OrderDO> orders = orderMapper.selectListByUserId(1);
        System.out.println(orders.size());
    }

    @Test
    public void testInsert() {
        OrderDO order = new OrderDO();
        order.setUserId(1);
        orderMapper.insert(order);
    }

}

① testSelectById() 测试方法
执行日志如下:

2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Rule Type: sharding
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT
             
        id, user_id
     
        FROM orders
        WHERE id = ?
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : SQLStatement: ShardingSelectOptimizedStatement(tables=Tables(tables=[Table(name=orders, alias=Optional.absent())], schema=Optional.absent()), groupBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.groupby.GroupBy@2f054f70, orderBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.orderby.OrderBy@6467ddc7, selectItems=SelectItems(startIndex=29, stopIndex=39, distinctRow=false, items=[ColumnSelectItem(owner=null, name=id, alias=Optional.absent()), ColumnSelectItem(owner=null, name=user_id, alias=Optional.absent())], tables=[TableSegment(startIndex=60, stopIndex=65, name=orders, quoteCharacter=NONE, owner=Optional.absent(), alias=Optional.absent())]), pagination=org.apache.shardingsphere.core.optimize.sharding.segment.select.pagination.Pagination@27b337bb, containsSubquery=false)
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-0 ::: SELECT
             
        id, user_id
     
        FROM orders_0
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-0 ::: SELECT
             
        id, user_id
     
        FROM orders_2
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-0 ::: SELECT
             
        id, user_id
     
        FROM orders_4
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-0 ::: SELECT
             
        id, user_id
     
        FROM orders_6
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: SELECT
             
        id, user_id
     
        FROM orders_1
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: SELECT
             
        id, user_id
     
        FROM orders_3
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: SELECT
             
        id, user_id
     
        FROM orders_5
        WHERE id = ? ::: [1]
2020-07-01 21:46:48.441  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: SELECT
             
        id, user_id
     
        FROM orders_7
        WHERE id = ? ::: [1]
null
  • 明明只有一条 Logic SQL 操作,却发起了 8 条 Actual SQL 操作。这是为什么呢?

  • 我们使用 id = ? 作为查询条件,因为 Sharding-JDBC 解析不到我们配置的 user_id 片键(分库分表字段),作为查询字段,所以只好 全库表路由 ,查询所有对应的数据节点,也就是配置的所有数据库的数据表。这样,在获得所有查询结果后,通过 归并引擎 合并返回最终结果。

    通过将 Actual SQL 在每个数据库的数据表执行,返回的结果都是符合条件的。

    这样,和使用 Logic SQL 在逻辑表中执行的结果,实际是一致的。

  • 那么,一次性发起这么多条 Actual SQL 是不是会顺序执行,导致很慢呢?实际上,Sharding-JDBC 有 执行引擎 ,会并行执行这多条 Actual SQL 操作。所以呢,最终操作时长,由最慢的 Actual SQL 所决定。

  • 虽然说,执行引擎 提供了并行执行 Actual SQL 操作的能力,我们还是推荐尽可能查询的时候,带有片键(分库分表字段)。对 Sharding-JDBC 性能感兴趣,可以看看 《Sharding-JDBC 性能测试报告》

#testSelectListByUserId() 测试方法
执行日志如下:

2020-07-01 21:46:48.360  INFO 7344 --- [           main] ShardingSphere-SQL                       : Rule Type: sharding
2020-07-01 21:46:48.360  INFO 7344 --- [           main] ShardingSphere-SQL                       : Logic SQL: SELECT
             
        id, user_id
     
        FROM orders
        WHERE user_id = ?
2020-07-01 21:46:48.360  INFO 7344 --- [           main] ShardingSphere-SQL                       : SQLStatement: ShardingSelectOptimizedStatement(tables=Tables(tables=[Table(name=orders, alias=Optional.absent())], schema=Optional.absent()), groupBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.groupby.GroupBy@184823ed, orderBy=org.apache.shardingsphere.core.optimize.sharding.segment.select.orderby.OrderBy@30bbcf91, selectItems=SelectItems(startIndex=29, stopIndex=39, distinctRow=false, items=[ColumnSelectItem(owner=null, name=id, alias=Optional.absent()), ColumnSelectItem(owner=null, name=user_id, alias=Optional.absent())], tables=[TableSegment(startIndex=60, stopIndex=65, name=orders, quoteCharacter=NONE, owner=Optional.absent(), alias=Optional.absent())]), pagination=org.apache.shardingsphere.core.optimize.sharding.segment.select.pagination.Pagination@3ca14cf4, containsSubquery=false)
2020-07-01 21:46:48.360  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: SELECT
             
        id, user_id
     
        FROM orders_1
        WHERE user_id = ? ::: [1]
2
  • 一条 Logic SQL 操作,发起了 1 条 Actual SQL 操作。这是为什么呢?
  • 我们使用 user_id = ? 作为查询条件,因为 Sharding-JDBC 解析到我们配置的 user_id 片键(分库分表字段),作为查询字段,所以可以 标准路由 ,仅查询一个数据节点。这种,是 Sharding-JDBC 最为推荐使用的分片方式。
    • 分库:user_id % 2 = 1 % 2 = 1 ,所以路由到 ds-orders-1 数据源。
    • 分表:user_id % 8 = 1 % 8 = 1 ,所以路由到 orders_1 数据表。
    • 两者一结合,只查询 ds-orders-1.orders_1 数据节点。

#testInsert() 测试方法

执行日志如下:

2020-07-01 21:46:48.104  INFO 7344 --- [           main] ShardingSphere-SQL                       : Rule Type: sharding
2020-07-01 21:46:48.105  INFO 7344 --- [           main] ShardingSphere-SQL                       : Logic SQL: INSERT INTO orders (
            user_id
        ) VALUES (
            ?
        )
2020-07-01 21:46:48.105  INFO 7344 --- [           main] ShardingSphere-SQL                       : SQLStatement: ShardingInsertOptimizedStatement(super=ShardingConditionOptimizedStatement(tables=Tables(tables=[Table(name=orders, alias=Optional.absent())], schema=Optional.absent()), shardingConditions=ShardingConditions(conditions=[ShardingCondition(routeValues=[ListRouteValue(columnName=user_id, tableName=orders, values=[1])])]), encryptConditions=EncryptConditions(conditions=[])), tables=Tables(tables=[Table(name=orders, alias=Optional.absent())], schema=Optional.absent()), insertColumns=ShardingInsertColumns(generateKeyColumnName=id, assistedQueryAndPlainColumnNames=[], regularColumnNames=[user_id]), values=[InsertValue(assignments=[ParameterMarkerExpressionSegment(startIndex=72, stopIndex=72, parameterMarkerIndex=0)])], generatedKey=Optional.of(GeneratedKey(columnName=id, generated=true, generatedValues=[485203831362158593])), units=[InsertOptimizeResultUnit(columnNames=[user_id, id], values=[ParameterMarkerExpressionSegment(startIndex=72, stopIndex=72, parameterMarkerIndex=0), ParameterMarkerExpressionSegment(startIndex=0, stopIndex=0, parameterMarkerIndex=1)], parameters=[1, 485203831362158593], startIndexOfAppendedParameters=1)])
2020-07-01 21:46:48.105  INFO 7344 --- [           main] ShardingSphere-SQL                       : Actual SQL: ds-orders-1 ::: INSERT INTO orders_1 (
            user_id
        , id) VALUES (?, ?) ::: [1, 485203831362158593]
  • 不考虑 广播表 的情况下,插入语句必须带有片键(分库分表字段),否则 执行引擎 不知道插入到哪个数据库的哪个数据表中。毕竟,插入操作必然是单库单表。
  • 我们会发现,Actual SQL 相比 Logic SQL 来说,增加了主键 id485203831362158593 。这是为什么呢?我们配置 orders 逻辑表,使用 SNOWFLAKE 算法生成分布式主键,而 改写引擎 在发现我们的 Logic SQL 并未设置插入的 id 主键编号,它会自动生成主键,改写 Logic SQL ,附加 id 成 Logic SQL 。

至此,我们已经完成了一个 Sharding-JDBC 的简单的分库分表的示例。

八、 源代码

本文源代码执行Apache2.0开源许可协议,可从Gitee下载

九、附录


  1. Springboot,Mybatis-Plus,shardingjdbc实现单列分库,多租户整合

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351