TPC-H作为一个OLAP的基准测试,相关的优化文章很多[1,2]。PolarDB-X作为一款分布式数据库,我们在成本低廉的OSS存储底座上,构建了一套成熟的列存SQL引擎,对外提供性能更好、性价比更高的查询分析加速能力。通过TPCH测试集,我们验证了这套新的查询系统在分析型加速场景的表现。 TPCH 100GB测试集在6种不同公有云
| 规格 | 8c32g * 2 | 8c32g * 4 | 16c64g * 2 | 16c64g * 3 | 16c64g * 4 | 16c64g * 6 |
|---|---|---|---|---|---|---|
| 总耗时(秒) | 99.82 | 48.55 | 50.31 | 33.00 | 25.54 | 20.56 |
TPCH 1TB测试集在3种不同公有云实例规格下的性能数据:
| 规格 | 3*16C128GB | 4*16C128GB | 6*16C128GB |
|---|---|---|---|
| 总耗时(秒) | 508.76 | 408.45 | 233.93 |
在之前的文章中,我们介绍了PolarDB-X列存查询引擎[3]。尽管追求TPC-H跑分并非最终目标,但成绩过于不理想也难以接受。在调研过程中我们发现,各大数据库厂商通常会在其官方网站上公布测试步骤和结果,但很少详细介绍执行计划。大概理由也很简单:对于优化器来说,尤其是join reorder,TPC-H被公认为一个相对简单的基准测试[4]。
这个现状导致我们不得不花了大量时间在各个数据库上重复搭建数据库环境、构造TPC-H数据、导入TPC-H数据、分析TPC-H执行计划、出具调研报告的过程。为了减少大家的工作量,通过本文,我们从官方视角解读PolarDB-X的TPC-H 1T列存执行计划。 需要说明的点
- 执行计划基于polardb-2.4.0_5.4.19-20240527_xcluster5.4.19-20240527 explain analyze的结果,对应的执行计划文本均已开源[5],后续版本可能会变化,以开源文本为准。
- 图中的数字为相应算子的输出行数。有两行数字的,第二行为runtime filter过滤掉的行数。
- 除非标明reverse,hash join默认都是右孩子会建哈希表,左孩子做探测。
- 为了方便展示,省略了图中的project算子。
- TPC-H通常会有order by limit,本身没有什么计算量,图中树根处会简化成一个算子。
前置知识
优化器
PolarDB-X的优化器[6]包含RBO、CBO,其中CBO是标准的Cascade style Top-Down优化器;基数估计是传统的基于独立性假设的估计模型;统计信息[7]包括直方图、TopN、NDV。当前版本的优化器架构为

PolarDB-X中的列存是索引的形态,在优化器中会以索引选择的方式进行列存索引的自动使用。当CBO优化出的最优单机物理执行计划B为AP查询且可以走列存索引时,会进入列存优化器:
- 将解析器解析出逻辑执行计划
A中的行存表替换为列存索引。 - 列存RBO进行应用子查询消除、条件推导、列裁剪等规则优化执行计划。
- 列存CBO进行逻辑变换、物理变换,并基于代价产生最优分布式执行计划。
列存优化器与MPP优化器都会产生分布式执行计划,不同点在于MPP优化器在物理执行计划B的基础上插入shuffle算子
MPP优化器基于执行计划B进行优化的原因是行存查询性能的决定性因素是DN,数据分布并不重要。同时与单机执行计划保持同样的执行路径,可以避免因执行计划差异导致MPP执行比单机执行更慢。
列存优化器基于执行计划A进行优化的原因是数据重分布作为列存查询速度的决定性因素,必须在join reorder进行考虑。
分布式执行计划
PolarDB-X的计算节点是MPP架构,执行器会将同样的数据调度到同一个计算节点处理,方便复用缓存。调度的粒度可以是文件也可以是分区。对于分区级的调度,分区键相同的数据必然落到同一个计算节点,这就是所谓的partition wise。这种调度与表的分区方式相关,可以作为表的partition wise属性被优化器感知,并利用其最小化数据的重分布。
每个分区被分配到一个计算节点,这是经典的Balls into Bins问题。为了降低maximum load,在随机调度不均时会重新采用two choices的做法[8],用两倍的空间换取指数级的倾斜率下降。
Join
PolarDB-X的列存CBO会穷举所有可能的join顺序[9],同时提供semijoin-join交换、agg-join交换等能力,确保join order合理。由于列存CBO的搜索空间为单机CBO✖️MPP CBO,为了避免优化器过于耗时,超过10张表时不会做join reorder。 Runtime filter对于Join的性能提升显著。
PolarDB-X的runtime filter并不是优化器产生的,代价模型也不会考虑runtime filter的过滤效果。
runtime filter是由执行器自适应构建的,所以只有真实执行才能知道是否使用了runtime filter,explain analyze时才会无法展示。
物理算子
对于Join算子,PolarDB-X支持Hash Join、Sort Merge Join、BKA Join和Nested loop Join。行存优化器会选择上述算子中代价最低的;列存优化器则会优先选择Hash Join,无法使用Hash Join就使用Nested loop Join。
对于Agg算子,PolarDB-X支持Hash Agg和Sort Agg。行存优化器会选择上述算子中代价最低的;列存优化器则只会用Hash Agg。
对于Window算子,PolarDB-X支持Hash Window和Sort Window。
Hash window无法覆盖所有的场景,所以列存优化器与行存优化器一样会在Hash Window和Sort Window中选择代价更低的物理算子。




