sharding-jdbc分库分表规则-多表查询
Posted 那个天真的人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sharding-jdbc分库分表规则-多表查询相关的知识,希望对你有一定的参考价值。
前言
上一篇介绍了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】。
流程分析
有了单表查询逻辑的分析,我们直接来研究多表联合查询的流程是怎样的。
通过sql解析发现有两张逻辑表名称:t_order 和 t_order_item,
发现两个条件: t_order.userId = 10 和 t_order.order_id = 1001发现sql中有多张表,创建复合路由引擎:ComplexRoutingEngine
单表路由及合并
遍历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这句代码,把两张表绑定在一起,接着重新来看下解析的流程:
通过sql解析发现有两张逻辑表名称:t_order 和 t_order_item。
路由
在真正路由之前,引擎会做一个判断,如果解析出来的所有逻辑表都在一个绑定规则中,那么取出一张逻辑表,然后走单表的路由,在这里,两张逻辑表绑定在一个规则中,也就是所谓的全绑定,这时候,引擎使用其中一张表走单表路由逻辑。此示例用t_order单表路由解析出如下结果:
ds_jdbc_0, t_order, t_order_1那么问题来了,怎么处理逻辑表t_order_item的物理映射呢?
路由重写
在路由完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分库分表规则-多表查询的主要内容,如果未能解决你的问题,请参考以下文章