支持的版本: 当前 (17) / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2

64.4. GIN 索引 #

64.4.1. 简介 #

GIN代表广义倒排索引(Generalized Inverted Index)。GIN旨在处理要索引的项目是复合值,并且索引要处理的查询需要搜索出现在复合项目中的元素值的情况。例如,这些项目可以是文档,而查询可以是搜索包含特定单词的文档。

我们使用单词项目来指代要索引的复合值,并使用单词来指代元素值。GIN始终存储和搜索键,而不是项目值本身。

一个GIN索引存储一组 (键,发布列表) 对,其中发布列表是键出现的行 ID 的集合。同一个行 ID 可以出现在多个发布列表中,因为一个项目可以包含多个键。每个键值仅存储一次,因此对于同一键多次出现的情况,GIN索引非常紧凑。

GIN从某种意义上说是广义的,即GIN访问方法代码不需要知道它加速的具体操作。相反,它使用为特定数据类型定义的自定义策略。该策略定义了如何从索引项目和查询条件中提取键,以及如何确定包含查询中某些键值的行是否真的满足查询。

一个优势GIN是它允许由数据类型领域的专家而不是数据库专家来开发具有适当访问方法的自定义数据类型。这与使用GiST.

的优势非常相似。GINPostgreSQL 中的GIN实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护。有关

的更多信息,请访问他们的网站

64.4.2. 内置操作符类 #GIN核心 PostgreSQL 发行版包含表 64.3中所示的GIN操作符类。(附录 F中描述的一些可选模块提供了额外的

操作符类。)GIN表 64.3. 内置

操作符类 名称
可索引操作符 array_ops
&& (anyarray,anyarray)
@> (anyarray,anyarray)
<@ (anyarray,anyarray)
= (anyarray,anyarray) jsonb_ops
@> (jsonb,jsonb)
@? (jsonb,jsonpath)
@@ (jsonb,jsonpath)
? (jsonb,text)
?| (jsonb,text[])
?& (jsonb,text[]) jsonb_ops
@> (jsonb,jsonb)
@? (jsonb,jsonpath)
jsonb_path_ops tsvector_ops

@@ (tsvector,tsquery)

jsonb 类型的两个操作符类中,jsonb_ops 是默认值。jsonb_path_ops 支持的操作符较少,但对于这些操作符,它提供了更好的性能。有关详细信息,请参见第 8.14.4 节

的优势非常相似。GIN64.4.3. 可扩展性 #GIN接口具有高度的抽象性,仅要求访问方法实现者实现被访问数据类型的语义。

层本身负责并发、日志记录和搜索树结构。GIN要使GIN访问方法正常工作,只需实现几个用户定义的方法,这些方法定义了树中键的行为以及键、索引项目和可索引查询之间的关系。简而言之,

将可扩展性与通用性、代码重用和清晰的接口结合在一起。GIN

操作符类必须提供两个方法

Datum *extractValue(Datum itemValue, int32 *nkeys, bool **nullFlags)

返回一个 palloc'd 的键数组,给定一个要索引的项目。返回的键的数量必须存储到 *nkeys 中。如果任何键可以为空,也请 palloc 一个 *nkeys bool 字段的数组,将其地址存储在 *nullFlags 中,并根据需要设置这些空标志。如果所有键都不为空,则可以将 *nullFlags 保持为 NULL(其初始值)。如果项目不包含键,则返回值可以为 NULL

Datum *extractQuery(Datum query, int32 *nkeys, StrategyNumber n, bool **pmatch, Pointer **extra_data, bool **nullFlags, int32 *searchMode)

返回一个 palloc'd 的键数组,给定一个要查询的值;也就是说,query 是可索引操作符右侧的值,该操作符的左侧是索引列。n 是操作符类中操作符的策略编号(请参阅第 36.16.2 节)。通常,extractQuery 需要参考 n 来确定 query 的数据类型以及它应该使用的方法来提取键值。返回的键的数量必须存储到 *nkeys 中。如果任何键可以为空,也请 palloc 一个 *nkeys bool 字段的数组,将其地址存储在 *nullFlags 中,并根据需要设置这些空标志。如果所有键都不为空,则可以将 *nullFlags 保持为 NULL(其初始值)。如果 query 不包含键,则返回值可以为 NULL

searchMode 是一个输出参数,允许 extractQuery 指定有关如何进行搜索的详细信息。如果 *searchMode 设置为 GIN_SEARCH_MODE_DEFAULT(这是调用之前初始化的值),则仅考虑至少匹配返回的键之一的项目作为候选匹配项。如果 *searchMode 设置为 GIN_SEARCH_MODE_INCLUDE_EMPTY,则除了包含至少一个匹配键的项目外,还会考虑根本不包含键的项目作为候选匹配项。(此模式对于实现 is-subset-of 操作符很有用,例如。)如果 *searchMode 设置为 GIN_SEARCH_MODE_ALL,则索引中的所有非空项目都被视为候选匹配项,无论它们是否与返回的键匹配。(此模式比其他两种选择慢得多,因为它需要扫描基本上整个索引,但它可能需要正确实现边界情况。在大多数情况下都需要此模式的操作符可能不是 GIN 操作符类的理想选择。)用于设置此模式的符号在 access/gin.h 中定义。

pmatch 是一个输出参数,用于在支持部分匹配时使用。要使用它,extractQuery 必须分配一个 *nkeys bool 的数组,并将其地址存储在 *pmatch 中。如果对应的键需要部分匹配,则数组的每个元素都应设置为 true,如果不需要,则设置为 false。如果 *pmatch 设置为 NULL,则 GIN 假定不需要部分匹配。变量在调用之前初始化为 NULL,因此不支持部分匹配的操作符类可以简单地忽略此参数。

一个操作符类还必须提供一个函数来检查索引项是否与查询匹配。它有两种形式,一种是布尔型 consistent 函数,另一种是三元型 triConsistent 函数。triConsistent 涵盖了这两个函数的功能,因此仅提供 triConsistent 就足够了。但是,如果布尔型变体的计算成本明显较低,那么提供两者可能会更有优势。如果仅提供布尔型变体,则会禁用某些依赖于在获取所有键之前拒绝索引项的优化。

bool consistent(bool check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], bool *recheck, Datum queryKeys[], bool nullFlags[])

如果索引项满足策略编号为 n 的查询操作符(或者可能满足,如果返回了重新检查指示),则返回 true。此函数不能直接访问索引项的值,因为GIN不显式存储项。相反,可用的是有关从查询中提取的哪些键值出现在给定索引项中的知识。check 数组的长度为 nkeys,这与先前由 extractQuery 为此 query 数据返回的键数相同。check 数组的每个元素都是 true,如果索引项包含相应的查询键,即如果 (check[i] == true),则 extractQuery 结果数组的第 i 个键存在于索引项中。传入原始的 query 数据,以防 consistent 方法需要查阅它,queryKeys[]nullFlags[] 数组也同样传入,这些数组先前由 extractQuery 返回。extra_dataextractQuery 返回的额外数据数组,如果没有则为 NULL

extractQueryqueryKeys[] 中返回空键时,如果索引项包含空键,则对应的 check[] 元素为 true;也就是说,check[] 的语义类似于 IS NOT DISTINCT FROMconsistent 函数可以检查相应的 nullFlags[] 元素,如果它需要区分常规值匹配和空匹配。

成功时,如果需要根据查询操作符重新检查堆元组,则应将 *recheck 设置为 true,如果索引测试是精确的,则设置为 false。也就是说,false 的返回值保证堆元组与查询不匹配;true 的返回值,并且 *recheck 设置为 false,则保证堆元组与查询匹配;true 的返回值,并且 *recheck 设置为 true,则表示堆元组可能与查询匹配,因此需要获取该堆元组,并通过直接针对原始索引项评估查询操作符来重新检查。

GinTernaryValue triConsistent(GinTernaryValue check[], StrategyNumber n, Datum query, int32 nkeys, Pointer extra_data[], Datum queryKeys[], bool nullFlags[])

triConsistentconsistent 类似,但在 check 向量中,每个键有三个可能的值,而不是布尔值:GIN_TRUEGIN_FALSEGIN_MAYBEGIN_FALSEGIN_TRUE 的含义与常规布尔值相同,而 GIN_MAYBE 表示该键是否存在未知。当存在 GIN_MAYBE 值时,只有当无论索引项是否包含相应的查询键,该项都肯定匹配时,函数才应返回 GIN_TRUE。同样,只有当无论是否包含 GIN_MAYBE 键,该项都肯定不匹配时,函数才必须返回 GIN_FALSE。如果结果取决于 GIN_MAYBE 条目,即不能根据已知的查询键确认或否定匹配,则函数必须返回 GIN_MAYBE

check 向量中没有 GIN_MAYBE 值时,GIN_MAYBE 返回值等同于在布尔型 consistent 函数中设置 recheck 标志。

此外,GIN 必须有一种方法来对索引中存储的键值进行排序。操作符类可以通过指定比较方法来定义排序顺序

int compare(Datum a, Datum b)

比较两个键(不是索引项!),并返回一个小于零、零或大于零的整数,表示第一个键小于、等于或大于第二个键。空键永远不会传递给此函数。

或者,如果操作符类不提供 compare 方法,则 GIN 将查找索引键数据类型的默认 btree 操作符类,并使用其比较函数。建议在仅用于一种数据类型的 GIN 操作符类中指定比较函数,因为查找 btree 操作符类会消耗一些周期。但是,多态 GIN 操作符类(例如 array_ops)通常不能指定单个比较函数。

一个操作符类用于GIN可以选择提供以下方法

int comparePartial(Datum partial_key, Datum key, StrategyNumber n, Pointer extra_data)

将部分匹配的查询键与索引键进行比较。返回一个整数,其符号表示结果:小于零表示索引键与查询不匹配,但应继续索引扫描;零表示索引键与查询匹配;大于零表示应停止索引扫描,因为不再可能匹配。提供了生成部分匹配查询的操作符的策略编号 n,以防需要其语义来确定何时结束扫描。此外,extra_dataextractQuery 创建的额外数据数组的对应元素,如果没有,则为 NULL。空键永远不会传递给此函数。

void options(local_relopts *relopts)

定义一组控制操作符类行为的用户可见参数。

options 函数传递一个指向 local_relopts 结构的指针,该结构需要填充一组特定于操作符类的选项。可以使用 PG_HAS_OPCLASS_OPTIONS()PG_GET_OPCLASS_OPTIONS() 宏从其他支持函数访问这些选项。

由于索引值的键提取和键的表示都GIN是灵活的,它们可能取决于用户指定的参数。

为了支持部分匹配查询,操作符类必须提供 comparePartial 方法,并且其 extractQuery 方法在遇到部分匹配查询时必须设置 pmatch 参数。有关详细信息,请参见第 64.4.4.2 节

上面提到的各种 Datum 值的实际数据类型因操作符类而异。传递给 extractValue 的项值始终是操作符类的输入类型,并且所有键值都必须是该类的 STORAGE 类型。传递给 extractQueryconsistenttriConsistentquery 参数的类型是策略编号标识的类成员操作符的右侧输入类型。这不必与索引类型相同,只要可以从中提取正确类型的键值即可。但是,建议这三个支持函数的 SQL 声明为 query 参数使用操作符类的索引数据类型,即使实际类型可能是其他类型,具体取决于操作符。

64.4.4. 实现 #

在内部,一个GIN索引包含一个基于键构建的 B 树索引,其中每个键是一个或多个索引项的元素(例如,数组的成员),并且叶页中的每个元组都包含指向堆指针 B 树(发布树)的指针,或者当列表小到足以与键值一起放入单个索引元组时,包含一个简单的堆指针列表(发布列表)。图 64.1 说明了 GIN 索引的这些组件。

PostgreSQL 9.1 开始,空键值可以包含在索引中。此外,对于根据 extractValue 为空或不包含键的索引项,索引中包含占位符 null。这允许应该查找空项的搜索执行此操作。

多列GIN索引通过在复合值(列号,键值)上构建单个 B 树来实现。不同列的键值可以是不同的类型。

图 64.1. GIN 内部结构


64.4.4.1. GIN 快速更新技术 #

更新一个GIN索引往往很慢,因为倒排索引的固有性质:插入或更新一个堆行会导致索引中插入许多行(每个从索引项中提取的键一个)。GIN能够通过将新元组插入到临时的、未排序的待处理条目列表中来推迟大部分工作。当表被清理或自动分析时,或者当调用 gin_clean_pending_list 函数时,或者如果待处理列表大于 gin_pending_list_limit 时,这些条目将使用与初始索引创建期间使用的相同批量插入技术移动到主GIN数据结构。这大大提高了GIN索引更新速度,即使算上额外的清理开销。此外,开销工作可以由后台进程而不是前台查询处理完成。

这种方法的主要缺点是,搜索必须扫描待处理条目的列表以及搜索常规索引,因此,大量的待处理条目列表将显著减慢搜索速度。另一个缺点是,虽然大多数更新速度很快,但导致待处理列表变得太大的更新将立即产生清理周期,因此会比其他更新慢得多。正确使用自动清理可以最大限度地减少这两个问题。

如果一致的响应时间比更新速度更重要,则可以通过关闭 fastupdate 存储参数来禁用待处理条目的使用,用于GIN索引。有关详细信息,请参见CREATE INDEX

64.4.4.2. 部分匹配算法 #

GIN 可以支持部分匹配查询,其中查询不确定一个或多个键的精确匹配,但可能的匹配落在键值的合理窄范围内(在 compare 支持方法确定的键排序顺序内)。extractQuery 方法不是返回要精确匹配的键值,而是返回要搜索的范围的下限键值,并将 pmatch 标志设置为 true。然后使用 comparePartial 方法扫描键范围。comparePartial 对于匹配的索引键必须返回零,对于仍然在要搜索的范围内的非匹配项返回小于零,如果索引键超出可能匹配的范围,则返回大于零。

64.4.5. GIN 提示和技巧 #

创建 vs. 插入

插入到GIN索引中可能会很慢,因为每个项目可能插入许多键。因此,对于批量插入到表中的操作,建议在完成批量插入后删除 GIN 索引并重新创建它。

当为GIN启用 fastupdate 时(详细信息请参见 第 64.4.4.1 节),惩罚会比不启用时要小。但是,对于非常大的更新,最好还是删除并重新创建索引。

maintenance_work_mem

构建GIN索引的时间对 maintenance_work_mem 设置非常敏感;在创建索引期间不应吝啬工作内存。

gin_pending_list_limit

在启用 fastupdate 的现有GIN索引中进行一系列插入操作期间,当列表增长超过 gin_pending_list_limit 时,系统将清理待处理条目列表。为了避免观察到的响应时间波动,最好在后台(即通过自动清理)进行待处理列表清理。可以通过增加 gin_pending_list_limit 或使自动清理更积极来避免前台清理操作。但是,扩大清理操作的阈值意味着如果发生前台清理,它将花费更长的时间。

可以通过更改存储参数来覆盖单个 GIN 索引的 gin_pending_list_limit,这允许每个 GIN 索引具有自己的清理阈值。例如,可以仅增加可以大量更新的 GIN 索引的阈值,否则可以减少阈值。

gin_fuzzy_search_limit

开发GIN索引的主要目标是在 PostgreSQL 中创建对高度可扩展的全文搜索的支持,并且通常在全文搜索返回非常大的结果集时会出现这种情况。此外,当查询包含非常频繁的单词时,这种情况经常发生,因此大的结果集甚至没有用处。由于从磁盘读取许多元组并对其进行排序可能会花费大量时间,因此这对于生产是不可接受的。(请注意,索引搜索本身非常快。)

为了方便控制此类查询的执行,GIN在返回的行数上有一个可配置的软上限:gin_fuzzy_search_limit 配置参数。默认情况下将其设置为 0(表示没有限制)。如果设置了非零限制,则返回的集合是整个结果集的子集,随机选择。

表示实际返回的结果数可能与指定的限制略有不同,具体取决于查询和系统随机数生成器的质量。

从经验来看,数千个值(例如,5000 — 20000)效果很好。

64.4.6. 限制 #

GIN假设可索引操作符是严格的。这意味着根本不会对空项目值调用 extractValue(而是自动创建一个占位符索引条目),并且也不会对空查询值调用 extractQuery(而是假定查询无法满足)。但是请注意,支持非空复合项目或查询值中包含的空键值。

64.4.7. 示例 #

64.4.2. 内置操作符类 #GIN先前在表 64.3中显示的操作符类。以下 contrib 模块也包含GIN操作符类

btree_gin

几个数据类型的 B 树等效功能

hstore

用于存储(键,值)对的模块

intarray

增强对 int[] 的支持

pg_trgm

使用三元组匹配的文本相似性

提交更正

如果您在文档中发现任何不正确、与您使用特定功能的经验不符或需要进一步澄清的内容,请使用此表单报告文档问题。