四、Sharding-JDBC 水平分库

  前面已经介绍过,水平分表是在同一个数据内,把同一个表的数据按一定规则拆到多个表中。在 sharding-jdbc 快速入门里,我们已经对水平分表进行实现,这里不再重复介绍。

1、水平分库

  前面已经介绍过,水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上。接下来看一下如何使用 Sharding-JDBC 实现水平分库,接下来继续对 sharding-jdbc 快速入门 中的例子进行完善。

1.1 拆分 order_db 库

  将原有的 order_db 库拆分为 order_db_1、order_db_2
四、Sharding-JDBC 水平分库_第1张图片

1.2 分片规则修改

  由于数据库拆分了两个,这里需要配置两个数据源。

  分库需要配置分库的策略,和分表策略的意义类似,通过分库策略实现数据操作针对分库的数据库进行操作。

server.port = 9095
server.servlet.context-path = /sharding-jdbc

spring.application.name = sharding-jdbc
spring.http.encoding.charset = utf-8
spring.http.encoding.enabled = true
spring.http.encoding.force = true
# 针对bean被重复定义,重复则覆盖
spring.main.allow-bean-definition-overriding = true  
# 和数据库字段进行印射,最终成为驼峰命名
mybatis.configuration.map-underscore-to-canel-case = true

## Sharding-jdbc 分片规则配置
# 定义多个数据源
spring.shardingsphere.datasource.names = m1,m2
# 数据库连接池
spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://127.0.0.1:3306/order_db_1?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = 123456

spring.shardingsphere.datasource.m2.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url = jdbc:mysql://127.0.0.1:3306/order_db_2?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.m2.username = root
spring.shardingsphere.datasource.m2.password = 123456

# 分库策略,以 user_id 为分片键,分片策略为 user_id % 2 + 1,user_id 为偶数操作 m1 数据源,某则操作 m2
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.sharding-column = user_id
spring.shardingsphere.sharding.tables.t_order.database-strategy.inline.algorithm-expression = m$->{user_id % 2 + 1}

# 指定t_order表的数据分布情况配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes = m$->{1..2}.t_order_$->{1..2}

# 指定t_order表的主键生成策略为SNOWFLAKE(雪花算法,实现全局主键自增)
spring.shardingsphere.sharding.tables.t_order.key-generator.column = order_id
spring.shardingsphere.sharding.tables.t_order.key-generator.type = SNOWFLAKE

# 指定t_order表的分片策略,分片策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column = order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression = t_order_$->{order_id % 2 + 1}

# 打开sql输出日志
spring.shardingsphere.props.sql.show = true

  分库策略定义方式如下:

# 分库策略,如何将一个逻辑表印射到多个数据源
spring.shardingsphere.sharding.tables.<逻辑表名称>.database-strategy.<分片策略>.<分片策略属性名> = #分片策略属性值

# 分表策略,如何将一个逻辑表映射为多个实际表
spring.shardingsphere.sharding.tables.<逻辑表名称>.table-strategy.<分片策略>.<分片策略属性名> = #分片策略属性值

1.3 Sharding-JDBC 的集中分片策略

  不管分库还是分表,策略基本一样。有以下集中分片策略:

  • standard:标准分片策略,对应 StandardShardingStrategy。提供对 SQL 语句中 =,IN 和 BETWEEN AND 的分片操作支持。StandardShardingStrategy 只支持单分片键,提供PreciseShardingAlgorithm 和 RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片。RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND 分片,如果不配置 RangeShardingAlgorithm,SQL 中的 BETWEEN AND 将按照全库路由处理。
  • complex:复合分片策略,对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =, IN 和 BETWEEN AND 的分片操作支持。ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
  • inline:行表达式分片策略,对应 InlineShardingStrategy。使用Groovy 的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发,如: t_user_$->{u_id % 8} 表示 t_user 表根据 u_id 模 8,而分成 8 张表,表名称为 t_user_0 到 t_user_7。
  • hint:Hint 分片策略,对应 HintShardingStrategy。通过 Hint 而非 SQL 解析的方式分片的策略。对于分片字段非 SQL 决定,而由其他外置条件决定的场景,可使用 SQL Hint 灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 Java API 和 SQL 注解(待实现)两种方式使用。
  • none:部分片策略,对应NoneShardingStrategy。不分片的策略。

本例中使用的是 inline 分片策略,其他请查阅官方文档:https://shardingsphere.apache.org/document/current/cn/features/sharding/concept/sharding/

2、实现

2.1 创建 2 个数据库

/*
SQLyog Ultimate v12.08 (32 bit)
MySQL - 8.0.11 : Database - order_db
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`order_db_1` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `order_db_1`;

/*Table structure for table `t_order_1` */

DROP TABLE IF EXISTS `t_order_1`;

CREATE TABLE `t_order_1` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单表1';

/*Data for the table `t_order_1` */

/*Table structure for table `t_order_2` */

DROP TABLE IF EXISTS `t_order_2`;

CREATE TABLE `t_order_2` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单表2';

/*Data for the table `t_order_2` */

CREATE DATABASE /*!32312 IF NOT EXISTS*/`order_db_2` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `order_db_2`;

/*Table structure for table `t_order_1` */

DROP TABLE IF EXISTS `t_order_1`;

CREATE TABLE `t_order_1` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单表1';

/*Data for the table `t_order_1` */

/*Table structure for table `t_order_2` */

DROP TABLE IF EXISTS `t_order_2`;

CREATE TABLE `t_order_2` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='订单表2';

/*Data for the table `t_order_2` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

2.2 测试

  因为这是在 Sharding-JDBC 快速入门中的项目进行测试,如果没看,请移步到 Sharding-JDBC 快速入门。

2.2.1 插入测试结果

四、Sharding-JDBC 水平分库_第2张图片
  从结果上可以看出,因为插入的是 user_id = 1L,因此 Sharding-JDBC 通过水平分库策略,定位到 m2(t_order_2)数据库,然后再通过水平分表策略,定位到具体的表中去;如果插入的是 user_id = 2L,则会定位到 m1 中。

2.2.2 查询测试结果
  • 查询代码
    @Test
    public void testSelect() {
        List orderIds = new ArrayList<>();
        orderIds.add(393345721874513920l);
        orderIds.add(393783366735888385L);
        List orders = orderMapper.selectOrderByIds(orderIds);
    }
  • 查询结果
    四、Sharding-JDBC 水平分库_第3张图片
      从结果上,生成了 4 条 SQL ,然而和我们预期有点不一样。为什么?不应该是定位到某一个数据库中吗?

      其实不然,因为我们分库策略中是以 user_id 作为分片键,而我们查询时并没有传入 user_id,因此 Sharding-JDBC 不知道具体数据是在哪个库中,所以会使用广播路由进行全库查询。要想指定库查询,则需要传入 user_id.

  • 新增 mapper 接口

    /**
     * 根据id查询订单
     * @param orderIds id集合
     * @return
     */
    @Select("")
    List selectOrderByOrderIdsAndUserId(@Param("orderIds") List orderIds, @Param("userId") Long userId);
  • 新增测试方法
    /**
     * 通过订单id和用户id查询
     */
    @Test
    public void testSelectByOrderIdsAndUserId() {
        List orderIds = new ArrayList<>();
        orderIds.add(393345721874513920l);
        orderIds.add(393783366735888385L);
        List orders = orderMapper.selectOrderByOrderIdsAndUserId(orderIds, 2L);
    }
  • 测试结果
    在这里插入图片描述
      该测试结果就定位到了特定的数据库中。因此,为了查询效率的话,在查询的时候最好添加库的分片键。

你可能感兴趣的:(分库分表)