OceanBase 4.0 解读:分布式查询性能提升,我们是如何思考的?

news2025/8/2 22:04:37

关于作者

王国平

OceanBase高级技术专家

目前OceanBase SQL引擎的技术负责人。2016年加入 OceanBase,负责SQL引擎的研发工作。2008年毕业于哈尔滨工业大学,2014年在新加坡国立大学获得博士学位,博士期间主要研究方向是数据库领域的(多)查询优化和处理。在加入OceanBase之前,曾经在华为从事数据库的研发工作。


性能是衡量数据库系统的重要指标之一,也是数据库系统领域一直备受关注的话题。在 OceanBase 3.x 版本中,OceanBase 已经实现了相对完善的优化器引擎、单机执行引擎、并行执行引擎和向量化执行引擎。在 2021年 5 月份,OceanBase 用这个版本打榜了 TPC-H,在数据分析型基准测试榜单的30000GB结果一栏,OceanBase 占据性能排行首位,其中代表着数据库核心性能的每小时执行请求数综合指标达到了 1526 万QphH@30,000GB。这次打榜充分证明了 OceanBase 的分布式查询能力性能,而且具备线性可扩展。

 

尽管如此,在整个 3.x 版本的大规模应用中,我们在部分业务场景中还是遭遇到了一些性能问题,比如在特定的分布式场景中生成了不优的执行计划、执行引擎对于不优的执行计划的容错能力、特定场景下没法充分利用所有的并行度来加快查询的执行等。为了解决这些问题,在 OceanBase 4.0 设计之初,我们就一直在思考,OceanBase 应该如何改进 SQL 引擎来提升分布式查询性能。分布式查询优化和分布式执行引擎从根本上决定了 SQL 引擎的分布式查询性能,下面我们从这两个方面来聊一聊我们的思考。

 

OceanBase 4.0 如何做分布式查询优化 ?

 

众所周知,查询优化是数据库内核开发的重点和难点,也是数据库查询性能的关键点。查询优化的作用是给帮助用户写的每一条 SQL,选择一个最优的执行计划。通常来说,一条 SQL 会有很多等价的执行计划,不同执行计划的性能可能会有数量级别的差异,所以查询优化很多时候从根本上就决定了查询的性能。OceanBase 是一个分布式关系数据库系统,这就意味着 OceanBase 天生就需要解决分布式查询优化的问题。在整个关系数据库系统中,查询优化一直是开发的难点,而分布式的查询优化就更加加剧了优化的难度。接下来我们来聊聊相比于单机查询优化,分布式查询优化的挑战在哪里。

 

▋ 分布式查询优化的挑战

 

分布式查询优化大大提升了计划枚举空间

 

在查询优化中,优化器的其中一个目标是需要给执行计划中的每个算子选择一种具体的实现方法。在单机的场景下,算子的实现方法只需要考虑单机的实现,但是在分布式的场景中,算子的实现方法除了要考虑单机实现之外,还需要考虑其分布式的实现。就拿数据库中的连接算子而言,在单机的场景中,通常的实现方法有hash join、merge join 和 nested loop join。在分布式的场景中,通常的实现方法有 partition wise join、partitial partition wise join、hash-hash distribution join 和 broadcast distribution join。这些分布式的实现方法正交上单机的实现方法就会大大增加分布式查询优化的计划枚举空间,会让整个分布式查询优化变得更加有挑战。

 

分布式查询优化需要维护更多的物理属性

 

在单机的查询优化中,算子序是一个非常重要的物理属性,因为算子的序可能会用来加速后续的一些算子的执行。算子序本质上就是运行完这个算子之后,数据库中的元组是不是按照特定的序输出的。举个简单的例子,对于索引(a,b,c)的扫描,因为在 OceanBase 中索引扫描是保序扫描,所以这个索引扫描之后的序就是(a,b,c)。算子序跟特定的算子实现有关系,而且它可能会影响后续算子的代价,所以在每个算子执行之后,查询优化都会维护序这个物理属性,并且在做计划裁剪的时候会保留有用序的执行计划。

在分布式查询优化中,除了序这个物理属性之外,另外一个物理属性就是分区信息。分区信息主要包括数据的分区方式以及每个分区的物理位置信息。分区信息从根本上决定了一个算子的分布式算法的选择,比如一个连接能不能做 partition wise join 是取决于连接键和表的分区信息的,所以分区信息同样可能也会影响后续算子的代价,所以在分布式查询优化中,除了维护序这个物理属性之外,我们还需要维护分区信息这个物理属性。分区信息的维护最终会影响计划裁剪和计划选择,同时也增加了整个分布式查询优化的复杂性。

 

分布式查询优化需要更加精准的分布式代价模型

 

在查询优化中,代价是衡量一个执行计划好坏的标准,通常代价代表了一个执行计划的执行时间或者对数据库系统资源的占用量,包括 CPU 资源、IO 资源、网络资源等。在单机执行中,代价模型通常只需要考虑 CPU 和 IO 就可以。但是在分布式的场景中,除了考虑 CPU 和 IO 的代价之外,还需要考虑网络传输代价、查询的并行度以及一些分布式特定优化场景的代价,比如 bloom filter 的代价计算等。这些因素从根本上提升了分布式代价模型设计和拟合的复杂性,也从一定程度上增加了整个分布式查询优化的复杂性。

 

▋ OceanBase 3.x的二阶段分布式查询优化方法

 

为了解决分布式查询优化带来的复杂性,跟业界的大部分解决方案类似,OceanBase 3.x 的版本采用二阶段的分布式查询优化方法。

第一阶段: 假设所有的表都是本地的,依赖已有的单机查询优化能力选择一个本地最优的执行计划。

第二阶段: 在固定连接顺序和本地算法的基础上,基于简单的分布式代价模型为每一个算子选择一个分布式算法。

下图展示了一个二阶段的分布式查询优化方法的例子,其中左边表代表的是第一阶段生成的本地最优的执行计划,右边代表的是第二阶段生成的分布式计划。对于Q1,在第一阶段,单机优化器选择了一个如左边所示的本地最优的执行计划,其中 MJ、HJ 和 HGBY 分别代表了 merge join、hash join 和 hash group by 的本地算法。在第二阶段,在固定连续顺序和本地算法的基础上,基于简单的分布式代价模型,为每一个算子选择了一个分布式算法。在这个例子中,为MJ节点选择了一个 partition wise join 的分布式连接算法,为 HJ 节点选择了一个 hash-hash 重分区的分布式连接算法。

 

create table R1(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;
create table R2(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;
create table R3(a int primary key, b int, c int, d int) partition by hash(b) partitions 5;
select R2.c, sum(R3.d) from R1, R2, R3 where R1.a = R2.a and R2.C = R3.C group by R2.C;

图片

 

二阶段分布式查询优化放大大简化了整个分布式查询优化的复杂度,但是 OceanBase 3.x 在大规模商用的过程中也遇到了很多因为二阶段导致的分布式查询优化不优的情况,下面我们总结了比较突出的两大类问题。

 

没有考虑分区信息导致选择了不优的本地算法

 

二阶段的分布式查询优化通常会因为第一阶段优化时没有考虑分区信息而选择了不优的本地算法。考虑如下图所示的一个查询 Q2 和它的第一阶段的计划,在第一阶段本地优化的时候,如果谓词 R1.c = 100 的选择率比较低,那么满足这个条件的 R1 的行数会比较少,这个时候优化器会选择 nested loop join 来执行这个查询,即对于满足条件的 R1 中的每一行,通过 R2 上的索引 idx 快速的获取满足条件的 R2 数据。但是在真实的执行过程中,我们发现 nested loop join 的执行时间远远比优化器估计的要大很多,原因是因为 R2 是一个包含 100 个分区的分区表,在执行 nested loop join 的过程中,对于 R1 中的每一行,都需要在 R2 的每个分区都执行一遍,那么这个执行时间其实会扩大 100 倍。如果我们把这个扩大 100 的执行时间考虑进去,那么最优的计划可能就是 hash join 而不是 nested loop join 了。在这个场景中,因为第一阶段的优化没有考虑分区信息,所以在第一阶段会错误的估计单机算子的代价,从而导致选择了不优的本地算法。

 

create table R1(a int primary key, b int, c int);
create table R2(a int primary key, b int, c int, index idx(b)) partition by hash(a) partitions 100;
Q2: select * from R1, R2 where R2.b = R1.b and R1.c = 100;
/*一阶段计划*/
| =============================================

|ID|OPERATOR        |NAME   |EST. ROWS|COST |
---------------------------------------------

|0 |NESTED-LOOP JOIN|       |970299   |85622|
|1 | TABLE SCAN     |r1     |990      |40790|

|2 | TABLE SCAN     |r2(idx)|1        |44   |
=============================================

Outputs & filters:
-------------------------------------

  0 - output([r1.a], [r1.b], [r1.c], [r2.a], [r2.b], [r2.c]), filter(nil),
      conds(nil), nl_params_([r1.b])
  1 - output([r1.b], [r1.c], [r1.a]), filter([r1.c = 100]),
      access([r1.b], [r1.c], [r1.a]), partitions(p0)
  2 - output([r2.b], [r2.a], [r2.c]), filter(nil),
      access([r2.b], [r2.a], [r2.c]), partitions(p0)

 

没有考虑分区信息导致选择了不优的连接顺序

 

二阶段的分布式查询优化通常因为在第一阶段没有考虑分区信息而选择了不优的连接顺序。考虑如下的一个查询 Q3 和它所对应的两个本地计划和分布式计划,其中第一个计划选择了 ((R2, R3), R1) 的连接顺序,第二个计划选择了 ((R1, R2), R3) 的连接顺序。如果不考虑分区信息,在第一阶段优化器可能会选择 ((R2, R3), R1) 这样的连接顺序,但是这个连接顺序经过第二阶段之后可能会产生更多的网络传输代价,如下图所示,表 R1、R2、R3 以及 R2 和 R3 的连接结果都需要经过网络传输。一个更好的连续顺序可能是 ((R1,R2), R3),因为这个连接顺序经过第二阶段之后只需要传输 R3 以及 R1 和 R2 的连接结果 (R1 和 R2 因为可以做 partition wise join,所以是不需要做网络传输的)。这种因为没有考虑分区信息而导致选错了错误的连接顺序的场景在我们的业务场景中也大量存在。

 

create table R1(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;create table R2(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;create table R3(a int primary key, b int, c int, d int) partition by hash(b) partitions 5;Q3: select R2.c, sum(R3.d) from R1, R2, R3 where R1.a = R2.a and R2.b = R3.b;

图片

图片

 

在如上的两个场景中,究其本质就是因为在第一阶段做优化的时候没有考虑分区信息而选择了不优的连接顺序和本地算法。通过这两个场景我们也了解到了二阶段的分布式查询优化方法的缺点是显而易见的,接下来我们来聊一聊 OceanBase 4.0 是如何做分布式查询优化来解决这个问题的。

 

▋ OceanBase 4.0的分布式查询优化

 

我们认为分布式查询优化一定要使用一阶段的方法,即要同时枚举本地算法和分布式算法并且使用分布式代价模型来计算代价,而不是通过分阶段的方式来枚举本地算法和分布式算法。OceanBase 4.0 重构了整个分布式查询优化方法,从原先的二阶段变成了一阶段的分布式查询优化方法。

为了方便我们描述一阶段的分布式查询优化方法,这里我们简单介绍一下 System-R 的 Bottom-up 的动态规划方法。给定一个 SQL 语句,System-R 用 bottom-up 的动态规划的方法来进行连接枚举和连接算法的选择。给定一个 N 张表的连接,该方法以 size 为驱动枚举每一个子集的执行计划。对于每一个枚举的子集,该方法通过如下的方式来获取最优的计划:

  • 枚举所有单机的连接算法,维护序这个物理属性,使用单机代价模型来计算代价。

  • 保留代价最小的计划和存在有用序的计划,一个计划的序是有用的当且仅当该序对后续算子的分配有用。

下图展示了一个 4 张表的连接枚举例子。该算法首先会枚举大小为 1 的基表的计划,对于每一张基表,该方法会枚举所有的索引并且保留代价最小和存在有用序的计划。然后该算法为枚举每个大小为 2 的子集的计划,比如在枚举 {R1,R2} 这两张表的连接的时候,该方法会考虑所有的单机的连接算法,然后再正交上所有 R1 和 R2 保留的计划,最终达到枚举所有执行计划的目的。以此类推,该算法会继续枚举直至大小为 4 的子集的计划都已经枚举完成。

 

图片

 

基于已有的单机的 System-R 的查询优化方法,OceanBase 4.0 的分布式查询优化按照如下的方式工作:

  1. 对于每一个枚举的子集,枚举所有算子的分布式算法,对于每一个分布式算法,OceanBase 使用分布式代价模型来计算代价,同时 OceanBase 会同时维护序和分区信息这两个物理属性。
  2. 对于每一个枚举子集,除了保留代价最小的计划,保留存在有用序的计划,同时还需要保留有存在有用分区信息的计划。一个分区信息是有用的当且仅当它对后续的算子有用。考虑下图所示的场景, 在该场景中,P1 采用了 HASH-HASH 重分区的 HASH JOIN 方法, P2 采用了对 R2 做 BROADCAST 的 HASH JOIN 方法,虽然 P2 的代价比 P1 的代价高,但是 P2 继承了 R1 的分区信息,对后续的 group by 算子是有用的,因此 P2 这个计划也会被保留。

 

create table R1(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;
create table R2(a int primary key, b int, c int, d int) partition by hash(a) partitions 4;
select R1.a, SUM(R2.c) from R1, R2 where R1.b = R2.b group by R1.a;

图片

 

OceanBase 4.0 使用了一阶段的分布式查询优化方法,相比于单机的查询优化,分布式查询优化的计划空间是非常大的。为了解决计划空间大的问题,OceanBase 4.0 发明了很多快速裁剪计划的方法以及新增了新的连接枚举算法来支持超大规模表的分布式计划枚举。 通过这些技术,OceanBase 4.0 大大减少了分布式计划空间,提升了分布式查询优化的性能。同时我们的实验结果也表明OceanBase 4.0 可以在秒级内完成 50 张表的分布式计划的枚举。

 

OceanBase 4.0如何提升分布式执行引擎性能 ?

 

相比于 OceanBase 3.x 版本,OceanBase 4.0 在执行引擎方面做了很多方面的工作,其中包括实现了新的分布式和单机算法(比如 null-aware hash anti-join、shared broadcast hash join、hash-based window function、partition bloom filter 等),完善了整个向量化引擎的实现,开发了极致的并行下压技术,开启了自适应技术的开发。这些引擎方面的工作都大大提升了分布式查询和单机查询的性能。在这里我们主要介绍一下 OceanBase 4.0 的自适应技术和并行下压技术。

 

▋ OceanBase 4.0 执行引擎开始朝着自适应的方向发展

 

在 OceanBase 的业务场景中,我们发现 OceanBase 执行引擎对优化器产生的不优的执行计划没有任何的容错能力,即一旦优化器产生了不优的执行计划,那么执行引擎在执行的时候是没办法做一些计划上的调整从而到达提升性能的目的。虽然我们通常说优化器的目的是给数据库的查询选择一个最优的执行计划,但是从数据库发展的历程来看,优化器自身存在很多解决不了的难题,比如优化器始终解决不了估行不准确的问题,所以优化器有可能会选到一个不优的执行计划甚至是一个非常差的执行计划。

为了解决这个问题,OceanBase 4.0 执行引擎开始朝着自适应的方向发展。自适应技术是指执行引擎根据当前的执行状态来识别出来一部分计划不优的场景,通过动态调整执行计划从而达到提升执行性能的目的。我们认为一个执行引擎发展到一定阶段一定要通过自适应技术来尽量解决优化器产生的不优的执行计划的问题,当然我们也不认为自适应技术能够解决掉所有的计划不优的场景。

OceanBase 4.0 实现了自适应的 Group by/Distinct 并行下压技术,它可以解决Group by/Distinct 并行下压场景中因为计划不优而导致的性能回退问题。在正式介绍该自适应技术之前,我们首先简单介绍一下 Group by/Distinct 并行下压技术。Group by/Distinct 并行下压技术是分布式执行中一种常见的并行下压技术,它的核心思想是提前把 Group by 算子下压下去做部分的数据预聚合,通过预聚合的方式可以减少网络传输从而达到提升性能的目的。 下图展示了一个 Group by 并行下压的执行计划的例子,其中 5 号算子就是下压的 Group by 算子,通过 5 号算子的预聚合可以减少 4 号算子网络传输从而达到性能提升的目的。但是这里需要注意的是 Group by 并行下压不一定会带来性能上的提升,有时候也会导致性能回退,主要原因是因为下压的 Group By 算子会引来额外的计算代价,所以只有当网络传输带来的性能提升超过下压的 Group By 带来的计算开销,Group by 的并行下压才会带来收益。

 

create table R1(a int primary key, b int, c int) partition by hash(a) partitions 4;
explain select b, sum(c) from R1 group by b;
| ==========================================================

|ID|OPERATOR                     |NAME    |EST. ROWS|COST|
----------------------------------------------------------

|0 |PX COORDINATOR               |        |1        |10  |
|1 | EXCHANGE OUT DISTR          |:EX10001|1        |10  |
|2 |  HASH GROUP BY              |        |1        |9   |
|3 |   EXCHANGE IN DISTR         |        |1        |9   |
|4 |    EXCHANGE OUT DISTR (HASH)|:EX10000|1        |8   |
|5 |     HASH GROUP BY           |        |1        |8   |
|6 |      PX PARTITION ITERATOR  |        |1        |7   |

|7 |       TABLE SCAN            |r1      |1        |7   |
==========================================================

Outputs & filters:
-------------------------------------

  0 - output([INTERNAL_FUNCTION(r1.b, T_FUN_SUM(T_FUN_SUM(r1.c)))]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(r1.b, T_FUN_SUM(T_FUN_SUM(r1.c)))]), filter(nil), rowset=256, dop=1
  2 - output([r1.b], [T_FUN_SUM(T_FUN_SUM(r1.c))]), filter(nil), rowset=256,
      group([r1.b]), agg_func([T_FUN_SUM(T_FUN_SUM(r1.c))])
  3 - output([r1.b], [T_FUN_SUM(r1.c)]), filter(nil), rowset=256
  4 - (#keys=1, [r1.b]), output([r1.b], [T_FUN_SUM(r1.c)]), filter(nil), rowset=256, dop=1
  5 - output([r1.b], [T_FUN_SUM(r1.c)]), filter(nil), rowset=256,
      group([r1.b]), agg_func([T_FUN_SUM(r1.c)])
  6 - output([r1.b], [r1.c]), filter(nil), rowset=256
  7 - output([r1.b], [r1.c]), filter(nil), rowset=256,
      access([r1.b], [r1.c]), partitions(p[0-3])

 

OceanBase 在之前的版本中都是优化器通过计算代价来决定是否要下压 Group by 算子,但是因为优化器有时会错误的估计行数,会导致出现没有正确的下压Group by 算子或者错误的下压了 Group by 算子的场景,最终导致执行性能次优。为了解决这个问题,OceanBase 4.0 引入了自适应的 Group by/Distinct 并行下压技术,其核心思想是让优化器总是下压Group by/Distinct算子,然后在执行的时候通过采样下压算子的一部分数据来决定是否跳过下压的 Group by/Distinct 算子。该技术的难点在于如何判断下压的算子是否具备足够好的预聚合能力。OceanBase 采用了控制下压算子的 HASH 表在 L3 cache 之内(控制 Hash 表的性能)以及多轮采样的策略(确保数据连续非聚合性带来的误判)来判断下压算子是否具备足够好的预聚合能力。其核心思想如下:

  • 下压算子 hash 表尽量维持在 L2 cache (1M) 内, 如果预聚合效果不好,标记该 hash表状态为舍弃。如果预聚合效果很好, 可以将 hash 表扩张到 L3 cache(10 M),如果执行过程中发现需要更大的内存,标记该 hash 表为舍弃状态。

  • 如果当前 hash 表的状态是舍弃状态, 返回 hash 表内所有行并释放,重新建 hash 表,开启下一轮的采样检查。

  • 如果连续 5 次采样检查预聚合效果都不好,就跳过当前下压的 Group by 算子。

这里需要注意的是,相比于完全不下压的场景,自适应的 Group by/Distinct 并行下压会引入一些额外的 overhead,主要是在执行时需要对下压的 Group By/Distinct 算子做一些采样和计算来判断是否需要跳过该算子,但是经过我们对各种数据分布的测试,这个额外的 overhead 基本上可以控制在 10% 之内,但是获取的性能提升是非常大的。

除了自适应的 Group by/Distinct 下压技术之外,当前 OceanBase 4.0 也在探索和实现更多新的自适应技术,包括自适应的创建和探测 bloom filter、自适应地调整 nested loop join 和 hash join,自适应地调整分布式的 broadcast 连接和分布式的 hash-hash 重分区连接等技术。我们相信这些自适应的技术会把 OceanBase 的执行引擎能力提升到一个新的级别,能够使整个执行引擎更加健壮,能够在优化器生成不优执行计划或者非常差的执行计划的时候提升整个查询的性能。

 

▋ OceanBase 4.0朝着极致的并行下压技术的方向发展

 

分布式场景中的并行下压技术是指通过下压算子的计算从而达到提升性能的目的。并行下压技术通常通过最大限度地利用并行度或者减少数据网络传输来提升分布式查询的性能。并行下压技术对分布式的查询性能提升是非常明显的,在很多场景中都有数量级别的性能提升。 前一个章节中介绍的 Group By/Distinct 并行下压技术就是一个比较典型的并行下压的场景。相比于 OceanBase 3.x 的版本,OceanBase 4.0 实现了一套非常完善的并行下压技术,基本上覆盖了分析类场景中的所有算子,包括 Group/Rollup/Window Function/Distinct 等。

下面这个表格比较了 OceanBase 在 3.x 版本和 4.0 版本的并行下压技术上的区别。

下压场景举例3.x版本4.0版本
Group by, 不存在有distinct去重的聚合函数select a, sum(d) from t group by a;支持支持
Group By, 存在有distinct去重的聚合函数select a, sum(distinct c),count(distinct d) from t group by a;不支持支持
Rollupselect a, sum(d) from t group by a rollup(b);不支持支持
Distinctselect distinct a from t;支持支持
WindowFunction select a, b, sum(d) over (partition by c) from t;不支持支持

OceanBase 4.0 中每个算子的并行下压技术的实现都是不一样的,考虑到并行执行的复杂性,每种实现都面临不一样的挑战。因为文章篇幅的原因,这里我们不一一介绍每一种并行下压技术,我们通过 OceanBase 对于处理包含 distinct 去重的聚合函数的三阶段并行下压技术来介绍一下并行下压技术的优势。考虑下图的例子,其中 Q1 包含了两个 distinct 去重的集合函数,在 OceanBase 3.x 的版本中,Q1 是没办法做任何的并行下压的,从 Q1 的执行计划中也可以看出来,所有的去重逻辑和聚合逻辑都是在 0 号算子中计算,而且 0 号算子是不具备任何并行的能力的,这会导致整体的执行性能很差。

 

create table R1(a int, b int, c int, d int, primary key(a,b)) partition by hash(b) partitions 4;
Q1: select sum(distinct c), sum(distinct d) from R1 where a = 5;
| =====================================================

|ID|OPERATOR                |NAME    |EST. ROWS|COST|
-----------------------------------------------------

|0 |SCALAR GROUP BY         |        |1        |2365|
|1 | PX COORDINATOR         |        |3960     |2122|
|2 |  EXCHANGE OUT DISTR    |:EX10000|3960     |1532|
|3 |   PX PARTITION ITERATOR|        |3960     |1532|

|4 |    TABLE SCAN          |r1      |3960     |1532|
=====================================================

Outputs & filters:
-------------------------------------

  0 - output([T_FUN_SUM(distinct r1.c)], [T_FUN_SUM(distinct r1.d)]), filter(nil),
      group(nil), agg_func([T_FUN_SUM(distinct r1.c)], [T_FUN_SUM(distinct r1.d)])
  1 - output([r1.c], [r1.d]), filter(nil)
  2 - output([r1.c], [r1.d]), filter(nil), dop=1
  3 - output([r1.c], [r1.d]), filter(nil)
  4 - output([r1.c], [r1.d]), filter(nil),
      access([r1.c], [r1.d]), partitions(p[0-3])

 

为了解决这种包含 distinct 的聚合函数的分布式执行性能,OceanBase 在 4.0 引入了三阶段并行下压的逻辑。我们用下图中包含一个 distinct 去重的聚合函数的场景来简单介绍一下三阶段并行下压的大体逻辑。三阶段并行下压逻辑主要包括三个阶段:

第一阶段: 下压 distinct 逻辑去做数据部分去重,这里对应了下图中的 6 号算子。

第二阶段: 按照去重列做一次数据重分区,然后做完全去重和部分预聚合计算,这里对应了下图中的 3~5 号算子。

第三阶段: 把第二阶段的结果做最终的聚合,这里对应了下图中的 0-2 号算子。
相比于不做任何的下压,这里三阶段并行下压有两个性能上的好处。首先三阶段并行下压可以最大限度地利用并行度去做数据去重和数据预聚合。其次通过下压 distinct 做数据部分去重可以减少网络传输。

 

create table R1(a int, b int, c int, d int, primary key(a,b)) partition by hash(b) partitions 4;
select sum(distinct c) from R1 where a = 5;
| ===========================================================

|ID|OPERATOR                      |NAME    |EST. ROWS|COST|
-----------------------------------------------------------

|0 |SCALAR GROUP BY               |        |1        |1986|
|1 | PX COORDINATOR               |        |1        |1835|
|2 |  EXCHANGE OUT DISTR          |:EX10001|1        |1835|
|3 |   MERGE GROUP BY             |        |1        |1835|
|4 |    EXCHANGE IN DISTR         |        |1        |1683|
|5 |     EXCHANGE OUT DISTR (HASH)|:EX10000|1        |1683|
|6 |      HASH GROUP BY           |        |1        |1683|
|7 |       PX PARTITION ITERATOR  |        |3960     |1532|

|8 |        TABLE SCAN            |r1      |3960     |1532|
===========================================================

Outputs & filters:
-------------------------------------

  0 - output([T_FUN_SUM(T_FUN_SUM(distinct r1.c))]), filter(nil),
      group(nil), agg_func([T_FUN_SUM(T_FUN_SUM(distinct r1.c))])
  1 - output([T_FUN_SUM(distinct r1.c)]), filter(nil)
  2 - output([T_FUN_SUM(distinct r1.c)]), filter(nil), dop=1
  3 - output([T_FUN_SUM(distinct r1.c)]), filter(nil),
      group(nil), agg_func([T_FUN_SUM(distinct r1.c)])
  4 - output([r1.c]), filter(nil)
  5 - (#keys=1, [r1.c]), output([r1.c]), filter(nil), dop=1
  6 - output([r1.c]), filter(nil),
      group([r1.c]), agg_func(nil)
  7 - output([r1.c]), filter(nil)
  8 - output([r1.c]), filter(nil),
      access([r1.c]), partitions(p[0-3]

 

上面我们介绍了只包括一个 distinct 去重的聚合函数的三阶段并行下压处理,这里有一个问题是如果包含多个 distinct 的聚合函数,三阶段下压技术是否还可以工作?答案是肯定的,这里的处理技巧在于对于包含 N 个 distinct 去重的聚合函数的场景,在第一阶段的时候,为每一个包含 distinct 的聚合函数,我们会冗余一份数据并且标记这一份数据属于这个聚合函数的,剩下的第二阶段和第三阶段的处理基本上都是类似的,会有一些实现上的小差别。下图展示了 OceanBase 中包含 2 个 distinct 的聚合函数的三阶段下压例子,其中 aggr_code 就是用来标记不同的 distinct 所冗余的数据。

 

create table R1(a int, b int, c int, d int, primary key(a,b)) partition by hash(b) partitions 4;select sum(distinct c), sum(distinct d) from R1 where a = 5;| ===========================================================|ID|OPERATOR                      |NAME    |EST. ROWS|COST|-----------------------------------------------------------|0 |SCALAR GROUP BY               |        |1        |13  ||1 | PX COORDINATOR               |        |2        |13  ||2 |  EXCHANGE OUT DISTR          |:EX10001|2        |12  ||3 |   HASH GROUP BY              |        |2        |11  ||4 |    EXCHANGE IN DISTR         |        |2        |10  ||5 |     EXCHANGE OUT DISTR (HASH)|:EX10000|2        |9   ||6 |      HASH GROUP BY           |        |2        |8   ||7 |       PX PARTITION ITERATOR  |        |1        |7   ||8 |        TABLE SCAN            |r1      |1        |7   |===========================================================Outputs & filters:-------------------------------------  0 - output([T_FUN_SUM(T_FUN_SUM(dup(r1.c)))], [T_FUN_SUM(T_FUN_SUM(dup(r1.d)))]), filter(nil), rowset=256,      group(nil), agg_func([T_FUN_SUM(T_FUN_SUM(dup(r1.c)))], [T_FUN_SUM(T_FUN_SUM(dup(r1.d)))])  1 - output([AGGR_CODE], [T_FUN_SUM(dup(r1.c))], [T_FUN_SUM(dup(r1.d))]), filter(nil), rowset=256  2 - output([AGGR_CODE], [T_FUN_SUM(dup(r1.c))], [T_FUN_SUM(dup(r1.d))]), filter(nil), rowset=256, dop=1  3 - output([AGGR_CODE], [T_FUN_SUM(dup(r1.c))], [T_FUN_SUM(dup(r1.d))]), filter(nil), rowset=256,      group([AGGR_CODE]), agg_func([T_FUN_SUM(dup(r1.c))], [T_FUN_SUM(dup(r1.d))])  4 - output([AGGR_CODE], [dup(r1.c)], [dup(r1.d)]), filter(nil), rowset=256  5 - (#keys=3, [AGGR_CODE], [dup(r1.c)], [dup(r1.d)]), output([AGGR_CODE], [dup(r1.c)], [dup(r1.d)]), filter(nil), rowset=256, dop=1  6 - output([AGGR_CODE], [dup(r1.c)], [dup(r1.d)]), filter(nil), rowset=256,      group([AGGR_CODE], [dup(r1.c)], [dup(r1.d)]), agg_func(nil)  7 - output([r1.c], [r1.d]), filter(nil), rowset=256  8 - output([r1.c], [r1.d]), filter(nil), rowset=256,      access([r1.c], [r1.d]), partitions(p[0-3])

 

分布式并行下压的场景是一个比较常见的客户场景,在 OceanBase 3.x 的版本中,我们也遇到了不少因为并行下压功能的不完善导致的分布式查询性能问题。我们相信在 OceanBase 4.0 可以很好地解决这类问题,提升分布式查询的性能。

 

写在最后

 

文章的最后,我们希望和大家分享,OceanBase 4.0 的分布式性能提升实际效果。相比于 OceanBase 3.x 版本,OceanBase 4.0 实现了全新的分布式代价模型和分布式查询优化框架、开发了一套非常完善的并行下压技术,开启了自适应技术的开发。这些技术的开发驱动一方面来自于我们对客户需求的理解,另一方面也来自于我们自己对分布式系统的理解。

为测试 Oceanbase 4.0 版本这些技术的工作效果,我们在 TPC-DS 100GB 上进行了测试,实验结果表明 OceanBase 4.0 的分布式性能提升效果显著,TPC-DS 100GB 的 99 个查询的执行时间总和从 918s 下降到了 270s ,在本文的最后,大家也可以看到 TPC-DS 100GB 上其中一部分查询在 OceanBase 3.x 版本和 4.0 版本的实际性能对比。

 

图片
TPC-DS 100GB 性能测试对比(OceanBase 3.x vs. 4.0)

 

以上是我们对 OceanBase 4.0 分布式性能查询价值及技术演进的思考。数据库的本质是基础软件,站在软件「使用者」的角度来看,我们希望在未来的 4.x 版本中,通过分布式查询优化和执行引擎技术的创新能力,帮助用户带来更易用的使用体验和更快速的查询性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/35003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++入门(2)-类与对象

类与对象初步认识类与对象一、面向过程与面向对象的区别二、类与结构体三、类的定义四、类的实例化五、类对象六、this指针七、构造函数八、析构函数九、拷贝构造函数十、运算符重载函数初步认识类与对象 一、面向过程与面向对象的区别 C语言是面向过程进行编程,注…

springboot整合其他项目

目录 一,集成Druid 学习地址: 主要讲监控 基于springboot视图渲染技术上增加代码 1.1 增加pom依赖 1.2 先在application.yml中添加Druid的数据源 1.3 其次在全局配置文件application.yml中添加所需的配置 配置截图: 配置解析 1.4 启动…

电脑录屏快捷键是什么?win10自带屏幕录制在哪

​在使用电脑的过程中,我们难免会遇到使用电脑录屏功能。有时候可能是想录制网课,有时候可能是想录制游戏的精彩操作,有时候可能只是想录制会议内容。 电脑录屏能够将重要的画面内容进行录制,十分的方便。但也有很多的小伙伴不清…

Python基础(三):PyCharm安装和使用

文章目录 PyCharm安装和使用 一、PyCharm的作用 二、PyCharm系统要求 三、下载和安装 四、PyCharm基本使用 五、PyCharm的基本设置 1、修改主题 2、修改代码文字格式 3、修改解释器 4、项目管理 PyCharm安装和使用 14天学习训练营导师课程:杨鑫《Python…

一、VSCode——免安装

介绍 Visual Studio Code支持可移植模式。此模式使 VS Code 创建和维护的所有数据都位于自身附近,因此可以跨环境移动。 此模式还提供了设置 VS Code 扩展的安装文件夹位置的方法,这对于阻止在 Windows AppData 文件夹中安装扩展的企业环境非常有用。 …

极市打榜|70G+已标注数据集出炉,油品泄露识别等全新算法上线!

极市打榜 算法打榜是极市平台推出的一种算法项目合作模式,至今已上线 100 产业端落地算法项目,已对接智慧城市、智慧工地、明厨亮灶等多个行业真实需求,算法方向涵盖目标检测、行为识别、图像分割、视频理解、目标跟踪、OCR等。 开发者报名…

CAN 协议控制器和物理总线之间的接口芯片SIT1040T 高速 CAN 总线收发器

CAN 协议控制器和物理总线之间的接口芯片SIT1040T 高速 CAN 总线收发器 CAN是最新进的现场总线,灵活性好,通讯可靠性高,抗干扰能力强,超长通信距离等优点,110个节点,兼带CAN-FD功能产品,容错电压可达-70V~ 70V,温度范围高达-40C ~ 150C最初应用于汽车电子,目前已广…

安信可Ai-WB1系列AT指令连接MQTT阿里云物联网平台

文章目录前言1 准备材料2 创建云端设备3 硬件连接4 配置终端设备5 MQTT实现发布&订阅消息联系我们前言 本文将介绍安信可AI-WB1系列通过AT指令接入阿里云物联网平台,实现MQTT的订阅和发布。 1 准备材料 AI-WB1系列模组或者开发板USB转TTL模块/Type-C数据线阿…

数仓建设教程

50000字,数仓建设保姆级教程,离线和实时一网打尽(理论实战) 上 - 腾讯云开发者社区-腾讯云 (tencent.com)50000字,数仓建设保姆级教程,离线和实时一网打尽(理论实战) 下_五分钟学大数据的技术博客_51CTO博客#yyds干货盘点#最强最全…

什么是DDoS攻击?企业服务器是否需要DDoS防御

有时候你可能会遇到某个网站突然打不开,这一段时间后发布自己被DDos攻击的公告, 那么,为什么DDOS攻击能让服务器瘫痪?黑客又如何执行DDos攻击的呢? DDoS全称为Distributed Denial of Service(分布式拒绝服…

Anemoi hash:一种SNARK-friendly的哈希函数

随着zk的兴起,出现了一大批zk友好且面向算术化(Arithmetization-Oriented)的哈希函数,如MiMC-Hash, Rescue–Prime, Poseidon等等,本文要介绍的Anemoi是今年新出的一种zk友好且面向算术化的哈希函数,与其他哈希函数相比&#xff0…

让我们进入面向对象的世界(三)

文章目录前言一.了解什么是继承二.我们针对继承来设计一个动物继承树前言2.1 第一步 找出共同属性和行为的对象2.2 设计代表共同状态行为的类2.3 决定子类是否让某项行为有不同的运作方式。2.4我们仔细去观察一下子类的特征,争取更多的抽象化的机会。三.继承的相关语…

leetcode:887. 鸡蛋掉落【经典dp定义】

目录题目截图题目分析ac code总结题目截图 题目分析 变量:鸡蛋的数量,楼层n,尝试的次数m有一个单调性容易发现:尝试的次数越多,能解决楼层越高的确切值另一个单调性:鸡蛋的数量越多,能够解决楼…

Flutter 实现局部刷新 StreamBuilder 实例详解

一、前言 在flutter项目中,页面内直接调用setState方法会使得页面重新执行build方法,导致内部组件被全量刷新,造成不必要的性能消耗。出于性能和用户体验方面的考虑我们经常会使用局部刷新代替全量刷新进行页面更新的操作。包括Provider、Va…

制造业企业如何高效进行生产计划排单?

随着社会的发展,个性化订单需求越来越多。面对如今更加多样化、紧迫化、随机化的订单,企业必须采用科学合理的方式和手段对生产计划进行控制,以提高企业的经济效益。生产计划在实施的过程中,由于一些原因,往往造成实施…

Node.js 模块化及npm概念介绍

文章目录模块化1 模块化的基本概念1.1 什么是模块化1.2 模块化规范2 Nodejs中的模块化2.1 Nodejs中模块的分类2.2 加载模块2.3 模块的作用域2.4 向外共享模块作用域中的成员2.4.1 module对象2.4.2 module.export对象2.4.3 共享成员时的注意点2.4.4 exports对象2.4.5 exportshe …

栈和队列及其多种接口实现-c语言

今天我们来完成栈和队列,首先我们要明白什么是栈,什么是队列。 目录 栈的选择 栈的结构 栈的初始化 栈的销毁 入栈 出栈 返回栈顶元素 计算数据个数 判断是否为空 队列的选择 队列的结构 入队列 出队列 判断是否为空 取队头元素 取队尾…

Java Spring Bean的生命周期 三级缓存

Java Spring Bean的生命周期 三级缓存 SpringBean的生命周期:是从 Bean 实例化之后(即通过反射创建出对象之后),到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring…

盘一盘那些年我们使用的Java

一、序 那些年我们使用过的Java版本。我是一个80后,当年在大学时代使用的是Java5,当时是大三的时候学校有了编程课,最开始学的是汇编语言、VB、C然后再是Java。当时就是Java5,搞了个课程设计与顺便也参加了个校园程序设计大赛。当…

MCE | Nrf2 的“戏精”之路

外界刺激 (如药物、紫外线和电离辐射) 和内源性自由基和活性氧 (ROS) 会直接或者间接地损伤蛋白质、脂质和 DNA 等细胞成分,为了抵御这些不利影响,机体形成了一套复杂的氧化应激应答系统来缓解细胞所受的损害。而 Nrf2,作为调控抗氧化应激的一…