场景

理论场景中常常须要依据一些常量指标做IN查问,并且IN值往往是分区键。例如在电商场景中,有两张表,买家表与订单表。订单的具体信息会记录到订单表中,该表依照订单ID进行哈希拆分;买家表则会保留买家ID及其关联的订单ID。一个买家常常须要查问其已购买的所有订单,一种广泛的做法是首先查问买家表获取该买家的所有订单ID,而后根据上述订单ID查问订单的具体信息。假如订单表有4个分片,买家A已下单的订单ID别离为1、 2、4、5和9,那么便会产生如下的SQL,即蕴含5个值的IN条件查问。

逻辑sql: select * from order where order_id in (1, 2, 4, 5, 9);

执行形式

不同于MySQL,PolarDB-X的架构能够分为计算层和存储层,存储层的DN 节点(数据节点)保留所有的数据。更多背景信息能够参考专栏文章 PolarDB-X 简介。

那么计算节点在收到这条逻辑SQL后,如何从DN节点拉取数据并进行计算呢?

Naive的执行形式

下发至所有分片的物理SQL中均蕴含所有的in值,如下所示。这种naive的执行形式会带来两个问题,一是随着订单表分片数的增多,须要扫描的分片数急剧减少,而该买家订单数据所在的分片数并未减少;二是随着in值的增多,下发的物理SQL在执行时会由索引扫描变为全表扫描,以上两个起因使得RT急剧升高。

分片1 物理sql: select * from order_01 where order_id in (1, 2, 4, 5, 9);分片2 物理sql: select * from order_02 where order_id in (1, 2, 4, 5, 9);分片3 物理sql: select * from order_03 where order_id in (1, 2, 4, 5, 9);分片4 物理sql: select * from order_04 where order_id in (1, 2, 4, 5, 9);

基于动静裁剪的执行

由上述探讨可知,咱们心愿能够裁剪掉肯定不蕴含所需数据的分片,因为in字段即为分区键,因而咱们能够应用分区算法计算每个in值所属分区,如下所示。

hash(1) = 1, hash(2) = 2, hash(4) = 4, hash(5) = 1, hash(9) = 1

以上述例子为例,显然咱们无需向分片3下发物理SQL。进一步地,因为分片1只可能蕴含订单ID为1、5和9的数据,而不可能蕴含订单ID为2或4的数据,于是咱们能够进一步对物理SQL中蕴含的in值进行裁剪。

当初咱们晓得了须要下发的SQL波及三个分片,那么如何下发这三条物理SQL呢,或者说下发SQL并期待后果的并行度是多少呢?显然,有两种极其,一是全副串行,该形式执行效率极低;二是全副并行,同时起无效分片数个线程向各个分片发送所有物理SQL,该形式的问题是如果分片数太多,会给零碎带来十分大的压力,同时会有大量的线程上下文切换。综合思考,咱们默认将下发物理SQL的并行度设置为机器的CPU核数,应用相似滑动窗口的形式下发物理SQL。

此外,咱们心愿下发到每个分片的物理SQL中蕴含的in值不要太多,否则该SQL在DN节点执行时很有可能进行全表扫描而非预期的索引扫描。因而当in值较多时,咱们会对in值进行切割,分批下发,执行流程如下图所示。

依然以上述的例子为例,假如咱们的CN节点只有两个核,假如每次下发的物理SQL中的in值不超过两个(当然理论中不会设置的这么小),那么咱们会共计下发四条物理SQL,如下所示,其会被划分为两个批次,也就是说计算节点和数据节点须要有两次网络交互。试想一下,如果咱们下发的物理SQL只有两条时,那么咱们便能够在一个批次中实现所有物理SQL的下发,此时计算节点和数据节点只有一次网络交互。因而,当用户对于RT要求较高时,咱们倡议in值数量该当较少,以保障其波及的分片数不超过CPU核数且每个分片只会下发一条物理SQL。

//第一次下发了两条物理sql分片1 物理sql-1: select * from order_01 where order_id in (1, 5);分片2 物理sql: select * from order_02 where order_id in (2);//第二次下发了两条物理sql分片1 物理sql-2: select * from order_01 where order_id in (9);分片4 物理sql: select * from order_04 where order_id in (4);

in值数量不固定带来的挑战

为了减速SQL的执行,咱们会对参数化SQL的执行打算进行缓存,而业务代码中的in值数量有时并不相同,这会使得执行打算的缓存空间可能会被仅是in值数量不同的SQL占满,导致其余SQL的执行打算生效。解决这个问题的思路也比较简单,咱们会用一个问号来代替in的列表,从而防止上述情况的产生。

测试

咱们在规格为2×16C64G的节点上,针对一张分表数为64,分表记录数为百万的表在不同in值数量、不同并发下进行了测试,分区形式为哈希分区,测试后果如下。

测试场景1-1

不同并发,不同in值数量,开启IN查问动静裁剪能力,查看RT变动。

测试场景1-2

不同并发,不同in值数量,敞开IN查问动静裁剪能力,查看RT变动。

测试场景2-1

不同并发,不同in值数量,开启IN查问动静裁剪能力,查看吞吐变动。

测试场景2-2

不同并发,不同in值数量,敞开IN查问动静裁剪能力,查看吞吐变动。

测试论断

  • 开启IN查问的动静裁剪能力后,吞吐和RT都有显著的改善。
  • 对于RT比拟敏感的客户,倡议in值数量不要取的太多。

总结

本篇首先介绍了in查问的一个经典利用场景,接着剖析了分布式数据库中执行in查问sql时naive的执行形式,其执行效率十分低下。为了进步执行效率,咱们进行了分区裁剪与in值裁剪。进一步地,思考到物理SQL中的in值太多会使得存储节点在执行物理SQL时有更大的可能进行全表扫描而非预期的索引扫描,因而当一个分片波及的in值较多时,咱们会将in值进行宰割,分批次下发in查问。为了避免并发量过大给零碎带来微小的压力,下发物理SQL的默认并发度为机器CPU核数。此外,咱们还针对in值数量不同可能导致打算缓存生效的问题进行了优化。最初,咱们测试了关上与敞开in查问动静裁剪的状况下,吞吐量与提早的变动,测试结果表明开启IN查问的动静裁剪能力后,吞吐和RT都有显著的改善。此外,咱们倡议对于RT比拟敏感的客户,in值数量不要取的太多。

作者:越寒

原文链接

本文为阿里云原创内容,未经容许不得转载。