索引访问方法必须在
IndexAmRoutine 中提供的索引构建和维护函数
有:
IndexBuildResult *
ambuild (Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
构建一个新索引。
索引关系已经被物理创建,但它是空的。
它必须被填入访问方法所需的任何固定数据,以及表中已存在的所有元组的
条目。
通常 ambuild 函数会调用
table_index_build_scan() 来扫描表中现有的元组
并计算需要插入索引的键。
该函数必须返回一个 palloc'd 的、包含新索引统计信息的结构。
amcanbuildparallel 标志表示访问方法是否
支持并行索引构建。设为 true 时,
系统会尝试为构建分配并行工作进程。
只支持非并行索引构建的访问方法应把这个标志保持为
false。
void ambuildempty (Relation indexRelation);
构建一个空索引,并把它写到给定关系的初始化 fork
(INIT_FORKNUM)中。
这个方法只对非日志索引调用;写到初始化 fork 中的空索引会在每次
服务器重启时被复制到主关系 fork 之上。
bool
aminsert (Relation indexRelation,
Datum *values,
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
IndexUniqueCheck checkUnique,
bool indexUnchanged,
IndexInfo *indexInfo);
向现有索引插入一个新元组。
values 和
isnull 数组给出要被索引的键值,
heap_tid 是要被索引的 TID。
如果访问方法支持唯一索引(其
amcanunique 标志为 true),那么
checkUnique 指示要执行的唯一性检查的类型。
这取决于唯一约束是否可延迟;详情见
Section 63.5。
通常,访问方法只有在执行唯一性检查时才需要
heapRelation 参数(因为那时它必须查看堆来验证
元组的存活状态)。
indexUnchanged 布尔值给出关于被索引元组性质的
提示。当它为 true 时,
该元组是索引中某个现有元组的重复。
新元组是逻辑上未改变的后继 MVCC 元组版本。
当发生一次不修改该索引覆盖的任何列、但仍然需要在索引中产生新版本的
UPDATE 时,就会出现这种情况。
索引 AM 可以利用这个提示来决定在同一个逻辑行的许多版本累积的索引
部分应用自底向上索引删除。
注意,更新一个非键列或一个只出现在部分索引谓词中的列不会影响
indexUnchanged 的值。
核心代码用一种低开销的方法确定每个元组的
indexUnchanged 值,该方法既允许假阳性也允许假
阴性。
索引 AM 不得把
indexUnchanged 当作关于元组可见性或版本化的
权威信息来源。
该函数的布尔结果值只在
checkUnique 为
UNIQUE_CHECK_PARTIAL 时才有意义。
这种情况下,真结果表示新条目被确知是唯一的,而假结果表示它可能
非唯一(必须安排一次延迟的唯一性检查)。
对其他情况,建议返回常量假。
某些索引可能不为所有元组建立索引。
如果某个元组不建立索引,aminsert 应当直接
返回而不做任何事。
如果索引 AM 希望在一条 SQL 语句内的相继索引插入之间缓存数据,
它可以在 indexInfo->ii_Context 中分配空间,
并把指向数据的指针存储在
indexInfo->ii_AmCache(其初始值为
NULL)中。
如果索引插入之后必须释放内存以外的资源,可以提供
aminsertcleanup,它会在内存被释放之前被
调用。
void
aminsertcleanup (Relation indexRelation,
IndexInfo *indexInfo);
清理在相继插入之间维护于
indexInfo->ii_AmCache 中的状态。
当数据需要额外的清理步骤(例如释放被钉住的缓冲区)、仅仅释放内存
不够时,这个方法很有用。
IndexBulkDeleteResult *
ambulkdelete (IndexVacuumInfo *info,
IndexBulkDeleteResult *stats,
IndexBulkDeleteCallback callback,
void *callback_state);
从索引中删除元组。
这是一个“批量删除”操作,预期的实现方式是扫描整个
索引并检查每个条目,判断它是否应当被删除。
必须以
callback(
的风格调用传入的
TID, callback_state) returns boolcallback 函数,来判断由其被引用 TID 标识的
特定索引条目是否要被删除。
必须返回一个 palloc'd 的、包含被删除条目统计信息的结构。
如果不需要把任何信息传给
amvacuumcleanup,返回 NULL 是可以的。
由于 maintenance_work_mem 有限,
当要删除许多元组时,可能需要多次调用
ambulkdelete。
stats 参数是该索引上一次调用的结果
(在 VACUUM 操作内的第一次调用时为 NULL)。
这允许 AM 在整个操作期间累积统计信息。
通常,如果传入的 stats 非空,
ambulkdelete 会修改并返回同一个结构。
IndexBulkDeleteResult *
amvacuumcleanup (IndexVacuumInfo *info,
IndexBulkDeleteResult *stats);
在 VACUUM 操作(零次或多次
ambulkdelete 调用)之后进行清理。
除了返回索引统计信息之外它不必做任何事,但它可能执行批量清理,
例如回收空的索引页面。
stats 是最后一次
ambulkdelete 调用返回的内容,或者如果因为
没有元组需要删除而未调用
ambulkdelete 则为 NULL。
如果结果非空,它必须是一个 palloc'd 的结构。
其中包含的统计信息将用于更新
pg_class,并且在给出
VERBOSE 时会被
VACUUM 报告。
如果索引在 VACUUM 操作期间完全没有变化,返回
NULL 是可以的,但否则应当返回正确的统计信息。
amvacuumcleanup 也会在
ANALYZE 操作完成时被调用。
这种情况下 stats 总是
NULL,任何返回值都会被忽略。
这种情形可以通过检查
info->analyze_only 来区分。
建议访问方法在这种调用中除了插入后的清理之外什么都不做,而且
只在 autovacuum 工作进程中做。
bool amcanreturn (Relation indexRelation, int attno);
通过返回该列原始的被索引值,检查索引能否支持在给定列上的
仅索引扫描。
属性号从 1 开始,即第一列的 attno 为 1。
支持则返回 true,否则返回 false。
对被包含的列(如果支持),这个函数应始终返回 true,因为无法取回
的被包含列没有什么意义。
如果访问方法完全不支持仅索引扫描,
其 IndexAmRoutine
结构中的 amcanreturn 字段可以设为
NULL。
void
amcostestimate (PlannerInfo *root,
IndexPath *path,
double loop_count,
Cost *indexStartupCost,
Cost *indexTotalCost,
Selectivity *indexSelectivity,
double *indexCorrelation,
double *indexPages);
估算索引扫描的代价。 这个函数在 Section 63.6 中有完整 描述。
int amgettreeheight (Relation rel);
计算树形索引的高度。
这一信息会以
path->indexinfo->tree_height 的形式提供给
amcostestimate 函数,并可用于支持代价
估算。
该结果不会在其他任何地方使用,因此这个函数实际上可以用来计算
代价估算函数可能想了解的关于索引的任何种类的数据(只要装进一个
整数)。
如果计算开销大,把结果缓存在
RelationData.rd_amcache 中可能会
有用。
bytea *
amoptions (ArrayType *reloptions,
bool validate);
为一个索引解析并验证 reloptions 数组。
只有当该索引存在非空的 reloptions 数组时才会调用它。
reloptions 是一个
text 数组,包含形如
name=value
的条目。
该函数应构造一个 bytea 值,它会被复制到索引 relcache
条目的 rd_options 字段中。
bytea 值的数据内容由访问方法自行定义;大多数标准
访问方法使用结构
StdRdOptions。
当 validate 为 true 时,如果任何选项无法
识别或具有无效值,该函数应报告合适的错误消息;
当 validate 为 false 时,无效条目应被静默
忽略。(加载已存储在
pg_catalog 中的选项时
validate 为 false;只有当访问方法修改了
其选项规则时才可能发现无效条目,那种情况下忽略过时条目是
合适的。)
如果想要默认行为,返回 NULL 是可以的。
bool
amproperty (Oid index_oid, int attno,
IndexAMProperty prop, const char *propname,
bool *res, bool *isnull);
amproperty 方法允许索引访问方法覆盖
pg_index_column_has_property
及相关函数的默认行为。
如果访问方法对索引属性查询没有任何特殊行为,
其 IndexAmRoutine 结构中的
amproperty 字段可以设为 NULL。
否则,amproperty 方法会以如下参数被调用:
对 pg_indexam_has_property 调用,
index_oid 和
attno 都为零;
对 pg_index_has_property 调用,
index_oid 有效而
attno 为零;
对 pg_index_column_has_property 调用,
index_oid 有效且
attno 大于
零。
prop 是标识被测试属性的枚举值,
而 propname 是原始的属性名字符串。
如果核心代码不能识别该属性名,
则 prop 为
AMPROP_UNKNOWN。
访问方法可以通过检查
propname 是否匹配来定义自定义属性名
(为了与核心代码一致,使用
pg_strcasecmp 匹配);对核心代码已知的名称,
最好检查 prop。
如果 amproperty 方法返回
true,
则它已经确定了属性测试结果:它必须把
*res 设为要返回的布尔值,或把
*isnull
设为 true 来返回 NULL。
(两个被引用的变量在调用前都被初始化为
false。)
如果 amproperty 方法返回
false,
则核心代码会继续用它的正常逻辑来确定属性测试结果。
支持排序操作符的访问方法应当实现
AMPROP_DISTANCE_ORDERABLE 属性测试,因为核心
代码不知道如何做这件事并且会返回 NULL。
实现 AMPROP_RETURNABLE 测试可能也有好处,
如果那样做比打开索引并调用
amcanreturn(核心代码的默认行为)更廉价的
话。
对所有其他标准属性,默认行为应当已经令人满意。
char * ambuildphasename (int64 phasenum);
返回给定构建阶段编号的文本名称。
这些阶段编号是通过
pgstat_progress_update_param 接口在索引构建
期间报告的那些。
阶段名随后会在
pg_stat_progress_create_index 视图中
公开。
bool amvalidate (Oid opclassoid);
在访问方法力所能及的范围内验证指定操作符类的目录条目。
例如,这可能包括测试是否提供了所有必需的支持函数。
如果操作符类无效,amvalidate 函数必须返回
false。
问题应当用 ereport 消息报告,通常在
INFO 级别。
void
amadjustmembers (Oid opfamilyoid,
Oid opclassoid,
List *operators,
List *functions);
在访问方法力所能及的范围内验证操作符族提议的新操作符和函数成员,
如果默认设置不令人满意则设置它们的依赖类型。
它在 CREATE OPERATOR CLASS 期间和
ALTER OPERATOR FAMILY ADD 期间被调用;在后一种
情况下 opclassoid 为
InvalidOid。
List 参数是
OpFamilyMember 结构的列表,其定义
在 amapi.h 中。
这个函数所做的测试通常是
amvalidate 所做测试的一个子集,
因为 amadjustmembers 不能假定它看到的是一个
完整的成员集合。
例如,检查一个支持函数的签名是合理的,但检查是否提供了所有必需的
支持函数就不合理了。
任何问题都可以通过抛出错误来报告。
OpFamilyMember 结构的依赖相关字段由核心
代码初始化为:如果是
CREATE OPERATOR CLASS,则对操作符类建立硬
依赖;如果是
ALTER OPERATOR FAMILY ADD,则对操作符族建立软
依赖。
如果某些其他行为更合适,amadjustmembers 可以
调整这些字段。
例如,GIN、GiST 和 SP-GiST
总是把操作符成员设置为对操作符族的软依赖,
因为在这些索引类型中操作符与操作符类之间的联系相对较弱;因此
允许自由地添加和移除操作符成员是合理的。
可选的支持函数通常也被赋予软依赖,这样必要时它们可以被
移除。
索引的目的当然是为匹配可索引
WHERE 条件(通常称为
限定符或
扫描键)的元组提供扫描支持。
索引扫描的语义在下文 Section 63.3 中有更完整
的描述。
索引访问方法可以支持
“普通”索引扫描、
“位图”索引扫描,或两者都支持。
索引访问方法必须或可以提供的与扫描相关的函数有:
IndexScanDesc
ambeginscan (Relation indexRelation,
int nkeys,
int norderbys);
为索引扫描做准备。
nkeys 和 norderbys
参数表示将在扫描中使用的限定符和排序操作符的数量;
这些可能对空间分配有用。
注意,扫描键的实际值此时还没有提供。
结果必须是一个 palloc'd 的结构。
出于实现原因,索引访问方法
必须通过调用
RelationGetIndexScan() 来创建这个结构。
在大多数情况下,
ambeginscan 除了进行那次调用(或许还有获取
锁)之外几乎不做别的事情;
索引扫描启动中有趣的部分在
amrescan 中。
void
amrescan (IndexScanDesc scan,
ScanKey keys,
int nkeys,
ScanKey orderbys,
int norderbys);
开始或重启一次索引扫描,可能带有新的扫描键。
(要用之前传入的键重启,为 keys 和/或
orderbys 传入 NULL。)
注意,键或排序操作符的数量不允许大于传给
ambeginscan 的数量。
在实践中,重启特性用于嵌套循环连接选定一个新的外层元组、因而需要
新的键比较值,但扫描键结构保持不变的情形。
bool
amgettuple (IndexScanDesc scan,
ScanDirection direction);
取回给定扫描中的下一个元组,按给定方向移动(在索引中向前或向后)。
如果取得了一个元组则返回 true,如果没有剩余的匹配元组则返回
false。真情况下元组 TID 被存入
scan 结构。
注意,“成功”只表示索引包含一个匹配扫描键的条目,
并不表示该元组必然仍存在于堆中或会通过调用者的快照测试。
成功时,amgettuple
还必须把 scan->xs_recheck 设为 true 或
false。
false 表示可以肯定索引条目匹配扫描键。
true 表示这一点不确定,扫描键所代表的条件在取回堆元组之后必须
重新检查。
这一机制支持“有损的”索引操作符。
注意,重新检查只扩展到扫描条件;部分索引谓词(如果有)永远不会
被 amgettuple 的调用者重新检查。
如果索引支持
仅索引
扫描(即 amcanreturn 对它的任何列
返回 true),
那么成功时 AM 还必须检查
scan->xs_want_itup,如果它为 true,则必须
返回该索引条目最初被索引的数据。
amcanreturn 对其返回 false 的列可以以空值的
形式返回。
数据可以以存储在 scan->xs_itup 的
IndexTuple 指针的形式返回,配套元组描述符
scan->xs_itupdesc;或者以存储在
scan->xs_hitup 的
HeapTuple 指针的形式返回,配套元组描述符
scan->xs_hitupdesc。
(当重建的数据可能放不进
IndexTuple 时应使用后一种格式。)
无论哪种情况,管理指针引用的数据都是访问方法的责任。
数据必须至少在扫描的下一次
amgettuple、amrescan 或
amendscan
调用之前保持有效。
只有当访问方法支持
“普通”索引扫描时才需要提供
amgettuple 函数。
如果不支持,
其 IndexAmRoutine
结构中的 amgettuple 字段必须设为
NULL。
int64
amgetbitmap (IndexScanDesc scan,
TIDBitmap *tbm);
取回给定扫描中的所有元组并把它们加入调用者提供的
TIDBitmap(即把元组 ID 集合 OR 进位图中已有的任何
集合)。
返回取回的元组数量(这可能只是一个近似计数,例如某些 AM 不检测
重复)。
在把元组 ID 插入位图时,amgetbitmap 可以
指示对特定元组 ID 需要重新检查扫描条件。
这类似于 amgettuple 的
xs_recheck 输出参数。
注意:在当前的实现中,对这个特性的支持与对位图本身有损存储的
支持混在一起,因此调用者会对可重查元组同时重新检查扫描条件和
部分索引谓词。
不过,这可能并不总是如此。
amgetbitmap 和
amgettuple 不能在同一次索引扫描中使用;使用
amgetbitmap 时还有其他限制,在
Section 63.3 中解释。
只有当访问方法支持
“位图”索引扫描时才需要提供
amgetbitmap 函数。
如果不支持,
其 IndexAmRoutine
结构中的 amgetbitmap 字段必须设为
NULL。
void amendscan (IndexScanDesc scan);
结束一次扫描并释放资源。
scan 结构本身不应当被释放,但访问方法内部
获取的任何锁或 pin 必须被释放,
ambeginscan 和其他扫描相关函数分配的任何其他
内存也必须释放。
void ammarkpos (IndexScanDesc scan);
标记当前扫描位置。 访问方法只需支持每次扫描记住一个扫描位置。
只有当访问方法支持有序扫描时才需要提供
ammarkpos 函数。
如果不支持,
其 IndexAmRoutine
结构中的 ammarkpos 字段可以设为
NULL。
void amrestrpos (IndexScanDesc scan);
把扫描恢复到最近标记的位置。
只有当访问方法支持有序扫描时才需要提供
amrestrpos 函数。
如果不支持,
其 IndexAmRoutine
结构中的 amrestrpos 字段可以设为
NULL。
除了支持普通索引扫描之外,某些类型的索引可能希望支持 并行索引扫描,它允许多个后端合作执行一次 索引扫描。 索引访问方法应当安排好一切,使每个合作进程返回普通、非并行索引 扫描将返回的元组的一个子集,并且使这些子集的并集等于普通、非并行 索引扫描会返回的元组集合。 此外,虽然并行扫描返回的元组不需要有任何全局排序,但每个合作 后端内返回的那部分元组的排序必须与请求的排序一致。 以下函数可以实现来支持并行索引扫描:
Size
amestimateparallelscan (Relation indexRelation,
int nkeys,
int norderbys);
估算并返回访问方法执行一次并行扫描所需的动态共享内存的字节数。
(这个数字是在
ParallelIndexScanDescData 中与 AM 无关的
数据所需空间之外额外需要的,而不是替代它。)
nkeys 和 norderbys
参数表示将在扫描中使用的限定符和排序操作符的数量;相同的值也会
传给 amrescan。
注意,扫描键的实际值此时还没有提供。
对不支持并行扫描的访问方法,或所需额外存储字节数为零的访问方法, 无需实现这个函数。
void aminitparallelscan (void *target);
这个函数会在并行扫描开始时被调用以初始化动态共享内存。
target 将指向至少
amestimateparallelscan 之前返回的字节数,
该函数可以使用这一数量的空间存储它想要的任何数据。
对不支持并行扫描的访问方法,或在所需共享内存空间无需初始化的 情况下,无需实现这个函数。
void amparallelrescan (IndexScanDesc scan);
如果实现了这个函数,它会在并行索引扫描必须重启时被调用。
它应当重置
aminitparallelscan 建立的任何共享状态,
使扫描能从头重新开始。
CompareType amtranslatestrategy (StrategyNumber strategy, Oid opfamily, Oid opcintype); StrategyNumber amtranslatecmptype (CompareType cmptype, Oid opfamily, Oid opcintype);
如果实现了这些函数,规划器和执行器会调用它们在固定的
CompareType 值与该访问方法使用的特定策略号之间
转换。
实现与内建 btree 或哈希访问方法类似功能的访问方法可以实现这些
函数;通过实现这些转换,系统可以了解访问方法操作的语义,
并能在各种场合用它们替代 btree 或哈希索引。
如果访问方法的功能与那些内建访问方法不相似,则无需实现这些
函数。
如果未实现这些函数,该访问方法在某些规划器和执行器决策中会被
忽略,但除此之外功能完全正常。