TSM 处理器函数返回一个 palloc 好的TsmRoutine
结构,其中包含
了下文所述的支持函数的指针。大部分函数是必须的,但是有些是可选的(它们
的指针可以为 NULL)。
void SampleScanGetSampleSize (PlannerInfo *root, RelOptInfo *baserel, List *paramexprs, BlockNumber *pages, double *tuples);
这个函数在规划期间被掉欧勇。它必须估计在一次采样扫描中会被读到的关系
页面数,以及将被该扫描所选择的元组数(例如,可以先估计采样分数,
乘上baserel->pages
和baserel->tuples
数,并且把结果圆整)。paramexprs
列表保存作为
TABLESAMPLE
子句的参数的表达式。如果出于优化的目的需要
这些表达式的值,我们推荐使用estimate_expression_value()
来尝试将这些表达式变成常量。但是即便这些表达不能被简化,该函数必须提供
估计的尺寸,并且即使出现不合法的值它也不应该失败(记住它们只是运行时值
的估计)。pages
和tuples
参数是输出。
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)
为真时,该
扫描将不会被真正执行,因此这个函数应该只做最少的事情,让该节点状态对
EXPLAIN
和EndSampleScan
可用。
这个函数可以被省略(把指针设置为 NULL),在那种情况下
BeginSampleScan
必须执行采样方法所需的所有初始化工作。
void BeginSampleScan (SampleScanState *node, Datum *params, int nparams, uint32 seed);
开始执行一次采样扫描。就在第一次尝试取得一个元组时就会调用这个函数,
如果该扫描需要被重启可能还要再次调用它。有关要扫描的表的信息可以通过
SampleScanState
节点的其他域访问(但是要注意
node->ss.ss_currentScanDesc
扫描描述符还没有被设置)。
长度为nparams
的params
数组包含在
TABLESAMPLE
子句中提供的参数的值。这些参数的编号和类型
在采样方法的parameterTypes
列表中指定,并且已经被
检查过不为空。seed
包含用于在采样方法中生成任何随机数的
种子。如果给定了REPEATABLE
值,种子将是该值的哈希。如果
没有指定REPEATABLE
值,种子将是random()
的
结果。
这个函数可能会调整域node->use_bulkread
以及node->use_pagemode
。
如果node->use_bulkread
为true
(默认),
该扫描将使用一种鼓励重用缓冲区的缓冲区访问策略。如果该扫描只访问
该表的页面的一小部分,将这个域设置为false
比较合理。
如果node->use_pagemode
为true
(默认),
那么对于每一个被访问的页面上的所有元组,该扫描将会在一趟中执行它们
的可见性检查。如果该扫描只选择每个被访问页面上的一小部分,将这个域
设置为false
比较合理。这将导致执行较少次的元组可见性检查,
但是每一次都会代价更大,因为需要更多锁定。
如果采样方法被标记为repeatable_across_scans
,在重
新扫描时,它必须能够选择和第一次扫描相同的元组集合,也就是说对
BeginSampleScan
的一次新调用必须选择和之前相同的元组
(如果TABLESAMPLE
参数和种子没有变化)。
BlockNumber NextSampleBlock (SampleScanState *node, BlockNumber nblocks);
返回下一个要扫描的页面的块号,如果没有剩余的页面需要扫描则返回
InvalidBlockNumber
。
这个函数可以被省略(设置指针为 NULL),在那种情况下核心代码将 执行整个关系的一次顺序扫描。这样一个扫描可以使用同步扫描,这样 采样方法不能假定每一次扫描都用同样的顺序访问关系页面。
OffsetNumber NextSampleTuple (SampleScanState *node, BlockNumber blockno, OffsetNumber maxoffset);
返回指定页面上下一个要被采样的元组的偏移量,如果没有剩余元组需要被采样,
则返回InvalidOffsetNumber
。maxoffset
是页面上
使用的最大偏移量。
NextSampleTuple
没有被显式地告知范围
1 .. maxoffset
中的哪些偏移量真正包含了合法的元组。这通常不
成问题,因为核心代码会忽略采样丢失或者不可见元组的请求。这不会导致采样
中的任何偏差。
不过,如果必要,该函数可以用node->donetuples
来检查它返回的元组有多少是合法并且可见的。
NextSampleTuple
不能假定
blockno
是最近一次NextSampleBlock
调用返回的
同一个页面号。它由之前某次NextSampleBlock
调用所返回,
但是核心代码被允许在真正扫描页面之前调用NextSampleBlock
,
以便支持预取。假定一旦一个给定页面的采样开始,连续的
NextSampleTuple
调用都将引用同一个页面(直到返回
InvalidOffsetNumber
)。
void EndSampleScan (SampleScanState *node);
结束扫描并且释放资源。释放 palloc 过的内存通常并不重要,但是任何外部 可见的资源应该被清除。在没有这类资源存在的通常情况下,这个函数可以被 省略(设置指针为 NULL)。