一. 前言
在openGauss中,TidScan是指过滤谓词中包含ctid信息时,不再全表扫描然后通过eqal比对的方式进行计划,而是通过那到ctid谓词,直接通过tid获取到元组,然后再应用其他的过滤条件来过滤通过ticd获取到的元组。如下所示:
本文主要讲述代码中是如何实现tidscan的。
二. 执行计划生成
执行计划生成的入口在set_plain_rel_pathlist,如下为主要的代码实现流程:
set_plain_rel_pathlist
if (rel->orientation == REL_ROW_ORIENTED)
create_tidscan_paths(root, rel);
tidquals = TidQualFromBaseRestrictinfo(rel);
foreach (l, rel->baserestrictinfo) {
rlst = TidQualFromExpr((Node*)rinfo->clause, rel->relid);
if (is_opclause(expr)) { // 等于类型的谓词条件
if (IsTidEqualClause((OpExpr*)expr, varno)) // 判断是不是谓词意见是ctid=xxx
if (node->opno != TIDEqualOperator) // 仅仅谓词条件包含tid时才支持生成tidscan
return false;
} else {
.... // 处理其他类型的谓词条件,比如in等场景
}
}
if (tidquals != NIL)
add_path(root, rel, (Path*)create_tidscan_path(root, rel, tidquals));
TidPath* pathnode = makeNode(TidPath);
pathnode->path.pathtype = T_TidScan;
cost_tidscan(&pathnode->path, root, rel, tidquals);
foreach (l, tidquals) {
if (IsA(lfirst(l), ScalarArrayOpExpr)) {
ntuples += estimate_array_length(arraynode); // 如果是in的场景,那么代价为数组的长度
else if (IsA(lfirst(l), CurrentOfExpr)) {
ntuples++; // 非数组的场景,tid访问的代价均为1
} else {
ntuples++;
}
}
}
三. TidScan算子实现
ExecTidScan的实现入口在ExecTidScan函数中,主要根据ctid的谓词条件转换成tid数组,依次取tid数组的位置信息拿到元组返回,主要流程如下所示:
ExecTidScan
ExecScan
ExecScanFetch
TidNext // 使用tid直接扫描元组
TidListCreateByRecursion(node, RELATION_CREATE_BUCKET(heap_relation)); // tid谓词条件转换成tid数组
if (expr && IsA(expr, ScalarArrayOpExpr)) {
for (i = 0; i < ndatums; i++) {
tid_list[num_tids++] = *itemptr; // 谓词条件转成的tid数组
}
}
tidstate->tss_TidList = tid_list; // 将需要扫描的数组保存在state中
tidstate->tss_NumTids = num_tids;
tidstate->tss_TidPtr = -1; // tss_TidPtr指的是当前取tss_TidList哪个下标值去读元组数据
TidFetchTuple
InitTidPtr(node, b_backward);
node->tss_TidPtr++; // 下边++,因此每次TidNext能拿到下一个tid对应的元组
ItemPointerData tid = tid_list[node->tss_TidPtr]; // 拿到元组的地址
tableam_tops_tuple_fetch_row_version // 根据tid拿到元组的数据
if (qual == NULL || ExecQual(qual, econtext)) { //应用其他的谓词条件,如第一章节中的job = 'ccc'等其他谓词条件
....
}