Hacking PostgreSQL 内核系列之三

核心提示1、一个分区表的问题 假如我们有一个分区表叫parent,里面只有一列数据,int类型的id。然后它是由三个分区表组成的--child1、child2和child3。他们分别是id从[0, 250),[250,500)以及[-1000
1、一个分区表的问题

假如我们有一个分区表叫parent,里面只有一列数据,int类型的id。然后它是由三个分区表组成的--child1、child2和child3。他们分别是id从[0, 250),[250,500)以及[-10000,0)。

这里我child1和child2都加了索引,但是child3没有。所以是child3上面先进行一个排序,然后index扫描child1和child2,然后再用一个merge append节点来把它们的结果汇集起来。

但是不对啊。我们的分区表是分段分区的。child1里面所有的东西一定都是小于child2的。child3里面的东西一定都是小于其他两个分区的。本来里面的都是已经排好序了(index scan其实就是一种排序),为什么最后需要用merge append对已经排好序的结果再排一次呢?merge append本身是使用priority tree来实现的,还是有点开销的。这里我们应该可以用一个简单的append节点来代替。

2、让我们来修改内核吧

首先我们要注意到这个问题是在排序的才会产生的。那么自然而然,我们就需要想办法在优化器中识别出这个状态。

什么样的情况可以增加我们想要的优化呢?我们要从一个分区表里面去排序地选择东西。这个分区表呢,是一个range partition table,而且它的每一个分区都没有子分区(因为那样实现起来太麻烦了)。

那么我们在优化器中该如何判断呢?首先应该想到的是PartitionBoundInfo这个结构体。它描述了分区表中每一个分区的上下限的关系,还有这个分区表的分区类型。

那么应该从哪儿下手呢?对源代码进行一点点搜索,你会发现这个函数:generate_mergeappend_paths。毕竟我们要把一个merge append的node变成一个append的node,从这个函数下手应该是合理的。

let's see some codes.

static voidgenerate_mergeappend_paths{ ListCell *lcp; bool is_range_partitioned = false; List *partition_keys = NIL; List *partition_keys_desc = NIL; PartitionBoundInfo bound_info = rel->boundinfo; if { is_range_partitioned = true; } if { partition_keys = generate_partition_pathkeys; partition_keys_desc = generate_partition_pathkeys; } foreach { List *pathkeys = lfirst; List *startup_subpaths = NIL; List *total_subpaths = NIL; bool startup_neq_total = false; ListCell *lcr; bool try_append = false; bool try_append_desc = false; if { if ) { try_append = true; } if ) { try_append_desc = true; } } foreach { RelOptInfo *childrel = lfirst; Path *cheapest_startup, *cheapest_total; cheapest_startup = get_cheapest_path_for_pathkeys; cheapest_total = get_cheapest_path_for_pathkeys; if { cheapest_startup = cheapest_total = childrel->cheapest_total_path; Assert; } if startup_neq_total = true; accumulate_append_subpath; accumulate_append_subpath; } //这里先不管desc的情况了。先让程序能够跑起来。 if { add_pathcreate_append_path); if { add_pathcreate_append_path); } } else { add_path create_merge_append_path); if add_path create_merge_append_path); } }}

这个代码实际上是我在原有的基础上改的,改动也不是很多。大家可以自己下载源代码来进行比对。

首先是这几个部分:

PartitionBoundInfo bound_info = rel->boundinfo; if { is_range_partitioned = true; } if { partition_keys = generate_partition_pathkeys; partition_keys_desc = generate_partition_pathkeys; }

这个是判断它是不是一个range partition table。如果是的话呢,就按照正反两个方向来生成两个需要对这个表排序的pathkey。在PostgreSQL中,pathkey是用来描述排序的变量。 generate_partition_pathkeys我是这样写的:

List *generate_partition_pathkeys{ PartitionScheme part_scheme = parent_rel->part_scheme; PartitionBoundInfo bound_info = parent_rel->boundinfo; if ) { // 我们只考虑range的情况。 //如果分区有默认分区的话,就需要merge append了。 return NIL; } List *result = NIL; for { PathKey *key; Expr *expr = linitial; key = make_pathkey_from_sortinfo, ScanDirectionIsBackward, 0, parent_rel->relids, false ); result = lappend; } return result;}

这个还是很好理解的。我们进入到了merge append了,不管怎么样我们总是要排序的。既然要排序,那就先把pathkey算出来吧。到时候可以用来做判断。

然后的程序就很简单了。我们对每一个subrelation去得到它的最佳的path。注意到这里的path有两个(对于每一个relation来说是cheapest_startup和cheapest_total) 。这个和最终优化器去算代价是有关系的。然后如果是我们需要的情况,那就用生成append path,然后加入到parent的pathlist中去。

我们的修改之旅到这里并没有结束。因为如果只是这样修改的话,那么结果会是这样的:

这里最上面的cost为0,主要是我太懒了没有更新cost计算的事情。

3、总结

这篇文章主要是我读了:

这里的讨论而写的。周末花了一天的时间看了前三个版本的实现,然后花了一天来简单实现一下。当然,写这个最主要的原因是我勇者斗恶龙11打通关了,周末没事做。偶尔看看技术文章挺好的。

 
友情链接
鄂ICP备19019357号-22