sharding-jdbc分库分表规则(2)-多表查询

来源:互联网 发布:多益网络客服电话 编辑:程序博客网 时间:2024/06/03 14:41

前言

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

建库建表

库 表 ds_jdbc_0 t_order_0,t_order_1,t_order_item_0,t_order_item_1 ds_jdbc_1 t_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_order TableUnit(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的绑定表机制在某些场景下确实带来了高效性。

原创粉丝点击