sharding-jdbc分库分表规则-多表查询

Posted 那个天真的人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sharding-jdbc分库分表规则-多表查询相关的知识,希望对你有一定的参考价值。

前言

上一篇介绍了sharding-jdbc单表的基本查询逻辑,本篇介绍一下多表的查询,包括多表查询的BindingTable机制。

建库建表

ds_jdbc_0t_order_0,t_order_1,t_order_item_0,t_order_item_1
ds_jdbc_1t_order_0,t_order_1,t_order_item_0,t_order_item_1



订单表逻辑语句:
CREATE TABLE IF NOT EXISTS t_order (order_id INT NOT NULL, user_id INT NOT NULL, status VARCHAR(50), PRIMARY KEY (order_id))

订单项逻辑语句:
CREATE TABLE IF NOT EXISTS t_order_item (item_id INT NOT NULL, order_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY (item_id))

配置

为简单起见,使用基本的jdbc进行操作,最精简的代码如下:

public final class MultiTableSelect 

    public static void main(final String[] args) throws SQLException 
        DataSource dataSource = getOrderShardingDataSource();
        printMultiTableSelect(dataSource);
    

    private static void executeUpdate(final DataSource dataSource, final String sql) throws SQLException 
        try (
                Connection conn = dataSource.getConnection();
                PreparedStatement preparedStatement = conn.prepareStatement(sql)) 
            preparedStatement.executeUpdate();
        
    
    private static ShardingDataSource getOrderShardingDataSource() 
        DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap());
        TableRule orderTableRule = TableRule.builder("t_order").actualTables(Arrays.asList("t_order_0", "t_order_1")).dataSourceRule(dataSourceRule).build();
        TableRule orderItemTableRule = TableRule.builder("t_order_item").actualTables(Arrays.asList("t_order_item_0", "t_order_item_1")).dataSourceRule(dataSourceRule).build();
        ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(Arrays.asList(orderTableRule,orderItemTableRule))
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())).build();
        Properties prop = new Properties();
        prop.setProperty(ShardingPropertiesConstant.SQL_SHOW.getKey(), "true");
        ShardingDataSource shardingDataSource = new ShardingDataSource(shardingRule , prop);
        return shardingDataSource;
    

    private static void printMultiTableSelect(final DataSource dataSource) throws SQLException 
        String sql = "SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.user_id=? AND o.order_id=?";
        try (
                Connection conn = dataSource.getConnection();
                PreparedStatement preparedStatement = conn.prepareStatement(sql)) 
            preparedStatement.setInt(1, 10);
            preparedStatement.setInt(2, 1001);
            try (ResultSet rs = preparedStatement.executeQuery()) 
                while (rs.next()) 
                    System.out.println(rs.getInt(1));
                    System.out.println(rs.getInt(2));
                    System.out.println(rs.getInt(3));
                
            
        
    
    private static DataSource createDataSource(final String dataSourceName) 
        BasicDataSource result = new BasicDataSource();
        result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
        result.setUrl(String.format("jdbc:mysql://127.0.0.1:3306/%s", dataSourceName));
        result.setUsername("root");
        result.setPassword("123456");
        return result;
    
    private static Map<String, DataSource> createDataSourceMap() 
        Map<String, DataSource> result = new HashMap<>(2);
        result.put("ds_jdbc_0", createDataSource("ds_jdbc_0"));
        result.put("ds_jdbc_1", createDataSource("ds_jdbc_1"));
        return result;
    

我们的目标是要执行下面这个sql语句:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.user_id=? AND o.order_id=?

运行时参数【10,1001】。

流程分析

有了单表查询逻辑的分析,我们直接来研究多表联合查询的流程是怎样的。

  1. 通过sql解析发现有两张逻辑表名称:t_order 和 t_order_item,
    发现两个条件: t_order.userId = 10 和 t_order.order_id = 1001

  2. 发现sql中有多张表,创建复合路由引擎:ComplexRoutingEngine

  3. 单表路由及合并
    遍历2张逻辑表 t_order 和 t_order_item
    根据逻辑表名获取TableRule,构建单表路由引擎SimpleRoutingEngine,
    然后获取单表路由结果,最后合并两个单表的路由结果

    此时两个表的路由如下:

    逻辑表路由结果
    t_orderTableUnit(dataSourceName=ds_jdbc_0, logicTableName=t_order, actualTableName=t_order_1)
    t_order_item[TableUnit(dataSourceName=ds_jdbc_0, logicTableName=t_order_item, actualTableName=t_order_item_0),

    TableUnit(dataSourceName=ds_jdbc_0, logicTableName=t_order_item, actualTableName=t_order_item_1),

    TableUnit(dataSourceName=ds_jdbc_1, logicTableName=t_order_item, actualTableName=t_order_item_0),

    TableUnit(dataSourceName=ds_jdbc_1, logicTableName=t_order_item, actualTableName=t_order_item_1)]



4. 笛卡尔路由

找出两个单表路由结果集的数据源交集,即t_order和t_order_item共有的数据源,在这里为 ds_jdbc_0。
在共有数据源中,找出涉及到的所有逻辑表:
ds_jdbc_0=[t_order, t_order_item]
转换为物理表:
[[t_order_1], [t_order_item_0, t_order_item_1]]

利用google提供的工具类 Sets.cartesianProduct 进行笛卡尔相乘,得到两组数据:
ds_jdbc_0.t_order_1 + ds_jdbc_0.t_order_item_0
ds_jdbc_0.t_order_1 + ds_jdbc_0.t_order_item_1

5. 生成执行单元
对于步骤4得到的两组数据,分别生成对应的SQLExecutionUnit,也就是最终我们要执行两条sql语句,最后执行后就是合并结果了,跟单表差不多,此处不再赘述。


其实,简单的多表查询跟单表查询差别并不是太大,主要是先利用了单表路由,然后合并路由,最后进行一个笛卡尔计算得到多条执行语句,这也告诉了我们,如果t_order和t_order_item分表非常多,而且你的查询条件并不能够减少最终路由到的物理表,那么结果就是这个笛尔卡积算出来的结果会非常的吓人,这也是官方提到的要注意的一个问题点,而为了解决这个问题,sharding-jdbc提供了BindingTable的机制来优化这个问题。

表的绑定

先来看看业务场景:订单记录t_order为一个大的订单,像购物车购买这种一下可以买多种商品的话,最终一般会进行拆单,拆成多条t_order_item记录,如果我们能够确保这两个表的路由规则是完全一样的话,实际上是可以避免完全的笛卡尔积的。

比如在每个数据源中,都有如下的物理表:
t_order_0,t_order_1,t_order_item_0,t_order_item_1

当一个订单id映射到 t_order_0的时候,业务能够确保其对应的t_order_item一定映射到t_order_item_0,这种情况下,t_order_0永远没有必要与t_order_item_1之类的物理表进行联合查询,BindingTable就是用于配置这种情况的。

首先要配置需要绑定在一起的表,如下代码所示:

ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(Arrays.asList(orderTableRule,orderItemTableRule))
                .bindingTableRules(Collections.singletonList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))))
                .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
                .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())).build();

我们新增了BindingTableRule这句代码,把两张表绑定在一起,接着重新来看下解析的流程:

  1. 通过sql解析发现有两张逻辑表名称:t_order 和 t_order_item。

  2. 路由
    在真正路由之前,引擎会做一个判断,如果解析出来的所有逻辑表都在一个绑定规则中,那么取出一张逻辑表,然后走单表的路由,在这里,两张逻辑表绑定在一个规则中,也就是所谓的全绑定,这时候,引擎使用其中一张表走单表路由逻辑。

    此示例用t_order单表路由解析出如下结果:
    ds_jdbc_0, t_order, t_order_1

    那么问题来了,怎么处理逻辑表t_order_item的物理映射呢?

  3. 路由重写
    在路由完t_order之后,引擎会进入sql重写阶段,要把sql中的t_order和t_order_item替换为物理表名,前面我们知道,我们找到了t_order_1,在重写的时候,引擎会用t_order去查找绑定规则,当找到了之后,计算t_order_1在所有物理表[t_order_0,t_order_1]中的位置索引,然后根据索引在物理表中[t_order_item0,t_order_item1]找到t_order_item的物理名表,这样便可以替换所有的逻辑表了。

混合绑定

前面介绍的要不就是非绑定多表关联,要不就是全绑定多表关联,如果一个查询中涉及到3张逻辑表,两张是绑定关系,另外一张没有绑定关系,那又该如何呢?

经过前面的分析,其实可以猜想到,由于有些表不是绑定表,所以肯定走复合路由,单表去路由后再合并笛卡尔计算,然而,由于绑定表的存在,在单表路由的时候,绑定的表只要路由任何其中一张就可以了,然后在最后重写sql的时候再根据彼此的索引去计算物理表名。

总结

表的关联有时候不可避免,但有时候我们也会业务上转化为单表多次去查询,这个要结合自身的业务去选择实现方案,sharding-jdbc的绑定表机制在某些场景下确实带来了高效性。

以上是关于sharding-jdbc分库分表规则-多表查询的主要内容,如果未能解决你的问题,请参考以下文章

sharding-jdbc分库分表规则-单表查询

sharding-jdbc分库分表规则-单表查询

sharding-jdbc 按月份分表

分库分表之sharding-jdbc

Sharding-JDBC分库分表异常排查

源码阅读——Sharding-JDBC分库分表中间件