Sharding-JDBC基础概念与分表不分库

分表不分库

基础概念

  • 逻辑表:水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
  • 真实表:在分片的数据库中真实存在的物理表,即上个示例中的t_order_0到t_order_9。
  • 数据节点:数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。
  • 绑定表:指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:
SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键order_id将数值10路由至第0片,将数值11路由至第1片,那么路由后的SQL应该为4条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系后,路由的SQL应该为2条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中t_orde在FROM的最左侧,ShardingSphere将会以它作为整个绑定表的主表。所有路由计算将会只使用主表的策略, 那么t_order_item 表的分片计算将会使用 t_order 的条件。

如何将两个表绑定呢?很简单,只要把他们的分区(包括分表和分库)键设置为同一个就可以了。

  • 广播表:指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

分片键

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。

分片算法

通过分片算法将数据分片,支持通过 =、>=、<=、>、<、BETWEEN 和 IN 分片。 分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。 由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

  • 标准分片算法:对应StandardShardingAlgorithm,用于处理使用单一键作为分片键的 =、IN、BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。
  • 复合分片算法:对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。
  • Hint分片算法:对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。

  • 标准分片策略:对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

  • 复合分片策略:对应ComplexShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

  • Hint分片策略:对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。 例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。

  • 不分片策略:对应 NoneShardingStrategy。不分片的策略。

分表不分库的实现

其他文件参考读写分离的。

application-sharding-table.yml的配置。

server:
  port: 8800

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    use-generated-keys: true

spring:
  shardingsphere:
    datasource:
      names: ds
      ds:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull
        username: master
        password: master
    sharding:
      tables:
        t_order:
          actual-data-nodes: ds.t_order_$->{0..1}
          table-strategy:
            inline:
              sharding-column: order_id
              algorithm-expression: t_order_$->{order_id % 2}
          key-generator:
            column: order_id
            type: SNOWFLAKE
            props:
              worker:
                id: 123

    props:
      sql:
        show: true # 打印sql

下面对代码和配置进行验证。

命令行执行如下命令:

$ curl localhost:8800/order/batchAdd
success

观察到数据库中t_order_0和t_order_1中各有5条数据。

使用下面的命令能将所有的数据都查出来:

$ curl localhost:8800/order/list
[{"orderId":548510135841435648,"userId":0,"status":"成功"},{"orderId":548510136147619840,"userId":2,"status":"成功"},{"orderId":548510136227311616,"userId":4,"status":"成功"},{"orderId":548510136252477440,"userId":6,"status":"成功"},{"orderId":548510136273448960,"userId":8,"status":"成功"},{"orderId":548510136135036929,"userId":1,"status":"成功"},{"orderId":548510136218923009,"userId":3,"status":"成功"},{"orderId":548510136239894529,"userId":5,"status":"成功"},{"orderId":548510136265060353,"userId":7,"status":"成功"},{"orderId":548510136286031873,"userId":9,"status":"成功"}]

自定义分表策略

分表策略=分片键+分片算法。

分片算法:

package com.morris.algorithm;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.text.SimpleDateFormat;
import java.util.*;

public class OrderPreciseShardingStrategy implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        Long orderId = preciseShardingValue.getValue();

        List<String> list = new ArrayList<>(collection);

        return list.get((int) (orderId% list.size()));
    }
}

配置文件中关键配置如下:

    sharding:
      tables:
        t_order:
          actual-data-nodes: ds.t_order_$->{0..1}
          table-strategy:
            standard:
              sharding-column: order_id
              precise-algorithm-class-name: com.morris.algorithm.OrderPreciseShardingStrategy
          key-generator:
            column: order_id
            type: SNOWFLAKE
            props:
              worker:
                id: 123

错误Sharding value must implements Comparable

### Cause: java.lang.IllegalArgumentException: Sharding value must implements Comparable.] with root cause

java.lang.IllegalArgumentException: Sharding value must implements Comparable.
	at com.google.common.base.Preconditions.checkArgument(Preconditions.java:122) ~[guava-18.0.jar:na]
	at org.apache.shardingsphere.core.route.router.sharding.condition.engine.InsertClauseShardingConditionEngine.getRouteValue(InsertClauseShardingConditionEngine.java:104) ~[sharding-core-route-4.0.0.jar:4.0.0]
	at org.apache.shardingsphere.core.route.router.sharding.condition.engine.InsertClauseShardingConditionEngine.createShardingCondition(InsertClauseShardingConditionEngine.java:88) ~[sharding-core-route-4.0.0.jar:4.0.0]
	at org.apache.shardingsphere.core.route.router.sharding.condition.engine.InsertClauseShardingConditionEngine.createShardingConditions(InsertClauseShardingConditionEngine.java:64) ~[sharding-core-route-4.0.0.jar:4.0.0]
	at org.apache.shardingsphere.core.route.router.sharding.ShardingRouter.getShardingConditions(ShardingRouter.java:129) ~[sharding-core-route-4.0.0.jar:4.0.0]
... ...

插入的SQL中不需要带主键,错误的写法如下,这么写就会报上面的错误:

    @Select("insert into t_order values(#{orderId}, #{userId}, #{status})")
    void insertWithId(Order order);

正确的写法:

    @Select("insert into t_order(user_id, status) values(#{userId}, #{status})")
    void insert(Order order);

Sharding JDBC会自动注入雪花算法生成的主键。

上一篇:分库分表 Sharding-JDBC (详解 2/6)


下一篇:分库分表下跨库join解决方案