共计 2657 个字符,预计需要花费 7 分钟才能阅读完成。
场景
理论场景中常常须要依据一些常量指标做 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 值数量不要取的太多。
作者:越寒
原文链接
本文为阿里云原创内容,未经容许不得转载。