Skip to content

60.1. 抽样方法支持函数#

TSM 处理器函数返回一个 palloc'dTsmRoutine结构,其中包含指向下面描述的支持函数的指针。大多数函数都是必需的,但有些是可选的,这些指针可以为 NULL。

void
SampleScanGetSampleSize (PlannerInfo *root,
                         RelOptInfo *baserel,
                         List *paramexprs,
                         BlockNumber *pages,
                         double *tuples);

此函数在规划期间调用。它必须估算在示例扫描期间将读取的关系页数,以及扫描将选择的元组数。(例如,这些可能通过估算抽样分数来确定,然后将baserel->pagesbaserel->tuples数字乘以该分数,确保将结果四舍五入为整数。)paramexprs列表保存作为TABLESAMPLE子句参数的表达式。建议使用estimate_expression_value()尝试将这些表达式简化为常量,如果其值对于估算目的而言是必需的;但该函数必须提供大小估算,即使它们无法简化,并且即使这些值似乎无效,它也不应失败(记住,它们只是对运行时值将是什么的估算)。pagestuples参数是输出。

void
InitSampleScan (SampleScanState *node,
                int eflags);

初始化以执行 SampleScan 计划节点。这在执行程序启动期间调用。它应执行在处理开始前所需的任何初始化。SampleScanState节点已创建,但其tsm_state字段为 NULL。InitSampleScan函数可以 palloc 抽样方法所需的任何内部状态数据,并将指向它的指针存储在node->tsm_state中。有关要扫描的表的信息可以通过SampleScanState节点的其他字段访问(但请注意,node->ss.ss_currentScanDesc扫描描述符尚未设置)。eflags包含描述此计划节点的执行程序操作模式的标志位。

(eflags & EXEC_FLAG_EXPLAIN_ONLY)为 true 时,扫描将不会实际执行,因此此函数应仅执行使节点状态对EXPLAINEndSampleScan有效所需的最小操作。

可以省略此函数(将指针设置为 NULL),在这种情况下,BeginSampleScan必须执行抽样方法所需的所有初始化。

void
BeginSampleScan (SampleScanState *node,
                 Datum *params,
                 int nparams,
                 uint32 seed);

开始执行抽样扫描。这在首次尝试获取元组之前调用,并且如果需要重新启动扫描,则可以再次调用。有关要扫描的表的信息可以通过SampleScanState节点的字段访问(但请注意,node->ss.ss_currentScanDesc扫描描述符尚未设置)。长度为nparamsparams数组包含在TABLESAMPLE子句中提供的参数的值。这些将具有抽样方法的parameterTypes列表中指定的数量和类型,并且已检查为非空。seed包含用于在抽样方法中生成的任何随机数的种子;如果给定REPEATABLE值,则它要么是源自该值的哈希,要么是random()的结果(如果没有)。

此函数可以调整字段node->use_bulkreadnode->use_pagemode。如果node->use_bulkreadtrue(默认情况下为 true),则扫描将使用缓冲区访问策略,该策略鼓励在使用后回收缓冲区。如果扫描只访问表页面的很小一部分,则可以合理地将此设置为false。如果node->use_pagemodetrue(默认情况下为 true),则扫描将在每个已访问页面上的所有元组中执行一次通过可见性检查。如果扫描只选择每个已访问页面上的一小部分元组,则可以合理地将此设置为false。这将导致执行更少的元组可见性检查,尽管每个检查的开销更大,因为它需要更多的锁定。

如果采样方法标记为repeatable_across_scans,则它必须能够在重新扫描期间选择与最初相同的元组集,即BeginSampleScan的新调用必须导致选择与之前相同的元组(如果TABLESAMPLE参数和种子没有更改)。

BlockNumber
NextSampleBlock (SampleScanState *node, BlockNumber nblocks);

返回要扫描的下一页的块号,如果没有剩余要扫描的页面,则返回InvalidBlockNumber

可以省略此函数(将指针设置为 NULL),在这种情况下,核心代码将对整个关系执行顺序扫描。此类扫描可以使用同步扫描,因此采样方法不能假设关系页面在每次扫描中都以相同的顺序访问。

OffsetNumber
NextSampleTuple (SampleScanState *node,
                 BlockNumber blockno,
                 OffsetNumber maxoffset);

返回指定页面上要采样的下一个元组的偏移号,如果没有剩余要采样的元组,则返回InvalidOffsetNumbermaxoffset是页面上使用的最大偏移号。

注意

NextSampleTuple没有明确告知1 .. maxoffset范围内的哪些偏移号实际上包含有效元组。这通常不是问题,因为核心代码会忽略对采样丢失或不可见元组的请求;这不会导致样本中的任何偏差。但是,如果需要,函数可以使用node->donetuples来检查返回的元组中有多少是有效且可见的。

注意

NextSampleTuple必须假设blockno是最近的NextSampleBlock调用返回的相同页号。它是由某个之前的NextSampleBlock调用返回的,但内核代码允许在实际扫描页面之前提前调用NextSampleBlock,以支持预取。可以假设,一旦给定页面的抽样开始,连续的NextSampleTuple调用都引用同一页面,直到返回InvalidOffsetNumber

void
EndSampleScan (SampleScanState *node);

结束扫描并释放资源。通常不必释放 palloc 分配的内存,但应清理任何外部可见的资源。在不存在此类资源的常见情况下,可以省略此函数(将指针设置为 NULL)。