第三章 解决方案设计与实现
3.1 总体思路
3.1.1 参数的传递思路
从第二章的分析可以看出,在进入ExecLimit(LimitState *node) 函数之后,先调用recompute_limits(node);来计算limit_count和limit_offset,但这两个数据都是存入了LimitState *node中,而没有传入到其子结点(sort)的执行状态中去。因此,如果要让PostgreSQL对limit_count和limit_offset进行优化,首先要解决的问题就如何把这两个参数传入进去。
对于传递参数,我们有两种方案,其一是修改状态数据结构,在其成员域里增加我们需要传的变量。其二是修改被调用的函数的参数列表,但为了修改此函数的时候不影响PostgreSQL的其他部分的调用,最好的方式是增加一个新的函数,用这个新的函数来代替以前的函数,并在其形参里增加几个我们要传入的变量。
3.1.2 执行流程修改思路
现在假设在ExecSort(SortState *node) 中已经通过上述的思路获得了需要的数据,那又应该如何修改函数执行的流程以取得我们需要的优化呢?
从上一章的流程详细分析中,我们看到,ExecSort是调用tuplesort_performsort(Tuplesortstate *state)来完成排序,因此考虑对这个函数进行修改,在tuplesort_performsort内部,当为内排序时,直接调用快速排序来完成。从这个流程中我们可以很轻易的看出内排序的修改的地方:假设limit_count和limit_offset两个参数成功的传入到tuplesort_performsort函数,则可以写一个函数来代替快速排序来全排序,而只是把需要返回的元组放到正确的位置即可。
下面我们就对针对上述思路来设计和实现具体的解决方案。
3.2 简单选择置换方案
3.2.1 方案设计思路
阅读快速排序可以知道,在qsort_arg(void *a, size_t n, size_t es, qsort_arg_comparator cmp, void *arg)中,只是对以地址a开始的n个元组数据,对每项长为es元组排序后还是放在a处。
参数传入之后,如果limit_count和offset比较小的话,便只需用简单的比较选择算法(0、limit_count+limit_offset)个元组到(0、limit_count+limit_offset)位置上。
下面就如何传入参数和修改流程两个方面,设计和实现具体的解决方案来实现上述简单的比较选择算法。
3.2.2 参数传递
在ExecLimit中,为了把limit_count 和limit_offset传入到ExecSort中,我们选择了第一种参数传递方案,即修改SortState结构体。在结构体增加三个域 bool hasLimit;int64 sort_count;int64 sort_offset;具体代码如下所示:
// execnodes.h typedef struct SortState { ScanState ss; /* its first field is NodeTag */ bool randomAccess; /* need random access to sort output? */ bool sort_Done; /* sort completed yet? */ void *tuplesortstate; /* private state of tuplesort.c */
//ouyang 12-12—在代码中搜索ouyang,即可得到所有修改的地方. bool hasLimit; //是否为limit结点排序 int64 sort_count; //只需要排序的项数.在limit中,为limit_count int64 sort_offset; //从第几项开始排序.在limit中,为limit_offset } SortState; |
图表 15:SortState修改关键代码及注释
然后,在ExecLimit(LimitState *node)的计算完count/offset之后,添加几行代码,以把这两个参数传入到SortState中。关键代码列表如下:
//nodeLimit.c TupleTableSlot * ExecLimit(LimitState *node) /* return: a tuple or NULL */ { TupleTableSlot *slot; PlanState *outerPlan; //获取子计划结点 outerPlan = outerPlanState(node); /* * ExecLimit状态变化及运动的逻辑主体 */ switch (node->lstate) { case LIMIT_INITIAL: //处理offset限制 //计算limit_count和offset等数据 recompute_limits(node); //判断参数是否合法 if (node->count <= 0 && !node->noCount) { node->lstate = LIMIT_EMPTY; return NULL; }
/*ouyang 12-11*/ if(outerPlan->type == T_SortState) { ((SortState *)outerPlan)->hasLimit = true; ((SortState *)outerPlan)->sort_count = node->count; ((SortState *)outerPlan)->sort_offset = node ->offset; } else { ((SortState *)outerPlan)->hasLimit = false; }
//处理至offset for (;;) { /*这里开始了第一次递归调用,在此递归调用中,会引有子计划结点的执行 根据我们的示例select * from teacher order by name limits 2 offset 1 和图,其子计划结点为T_SortState在即将运行的ExecProcNode中,将会运行result = ExecSort((SortState *) node); */ slot = ExecProcNode(outerPlan); if (TupIsNull(slot)) { //如果子计划返回的元组为空,即元组不够 node->lstate = LIMIT_EMPTY; return NULL; } //…… } /* * 我们已经通过执行子结点,获取了正确的元组,将状态修改为LIMIT_INWINDOW */ node->lstate = LIMIT_INWINDOW; //接下来返回的原组是满足要求的。 break; }
case LIMIT_INWINDOW: …… } return slot; } |
图表 16:ExecLimit修改关键代码及注释
最后,在ExecSort(SortState *node)函数中调用自己新添加的函数my_tuplesort_performsort(Tuplesortstate *state,int64 limit_count,int64 limit_offset),以传入两个加入的参数int64 limit_count,int64 limit_offset。
//nodeSort.c TupleTableSlot *ExecSort(SortState *node) { EState *estate; Tuplesortstate *tuplesortstate; TupleTableSlot *slot; //获取执行状态 estate = node->ss.ps.state; tuplesortstate = (Tuplesortstate *) node->tuplesortstate; //如果还没有排序,则排序 if (!node->sort_Done) { tuplesortstate = tuplesort_begin_heap(tupDesc, plannode->numCols, plannode->sortOperators, plannode->sortColIdx, work_mem, node->randomAccess); //分配空间
//ouyang 12-12—在第二种优化的选择方案中,将添加下面几行,在此方案中,还无须添加 /*tuplesortstate -> hasLimit = node ->hasLimit; tuplesortstate -> sort_count = node ->sort_count; tuplesortstate -> sort_offset = node ->sort_offset;*/ node->tuplesortstate = (void *) tuplesortstate; //下面的循环是调用其子计划结点来获取待排序的元组,在示例语句中,此子计划为T_SeqScan类型,由ExecSeqScan来执行 for (;;) { slot = ExecProcNode(outerNode); //在这里outerNode是一种方法取出数据库中数据,比方说ExecSeqScan() 方式 //运行:每一次从outerNode返回一个元组 if (TupIsNull(slot)) break; tuplesort_puttupleslot(tuplesortstate, slot); //放入刚取出的元组 } /*ouyang 12-10――当父计划结点hasLimit时,调用my_tuplesort_performsort 否则还调用以前的函数*/ if(node->hasLimit) { my_tuplesort_performsort(tuplesortstate,node ->sort_count,node->sort_offset); } else { tuplesort_performsort(tuplesortstate); } |