2025年9月25日: PostgreSQL 18 发布!
支持版本:当前 (18) / 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

65.4。GIN 索引 #

65.4.1。介绍 #

GIN代表通用倒排索引。GIN旨在处理要索引的项目是复合值的情况,并且索引需要处理的查询需要搜索复合项目中出现的元素值。例如,项目可以是文档,查询可以是搜索包含特定单词的文档。

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

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

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

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

GINPostgreSQL中的实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护。有关GIN的更多信息,请访问他们的网站

65.4.2。内置操作符类 #

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

表 65.3。内置GIN操作符类

名称 可索引操作符
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_path_ops @> (jsonb,jsonb)
@? (jsonb,jsonpath)
@@ (jsonb,jsonpath)
tsvector_ops @@ (tsvector,tsquery)

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

65.4.3。可扩展性 #

GIN接口具有高度抽象性,要求访问方法实现者只需实现被访问数据类型的语义。该GIN层本身负责并发、日志记录和搜索树结构。

要使GIN访问方法的工作是实现一些用户定义的方法,这些方法定义了树中键的行为以及键、索引项和可索引查询之间的关系。简而言之,GIN将可扩展性与通用性、代码重用和简洁的接口相结合。

操作符类必须提供两种方法:GIN必须提供

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

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

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

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

searchMode是一个输出参数,允许extractQuery指定搜索的详细信息。如果*searchMode设置为GIN_SEARCH_MODE_DEFAULT(这是调用前初始化的值),则只有与至少一个返回键匹配的项目才被视为候选匹配。如果*searchMode设置为GIN_SEARCH_MODE_INCLUDE_EMPTY,则除了包含至少一个匹配键的项目外,不包含任何键的项目也被视为候选匹配。(此模式对于实现子集操作符等非常有用。)如果*searchMode设置为GIN_SEARCH_MODE_ALL,则索引中所有非空项目都被视为候选匹配,无论它们是否与任何返回键匹配。(此模式比其他两种选择慢得多,因为它需要扫描几乎整个索引,但可能需要正确实现边缘情况。在大多数情况下需要此模式的操作符可能不适合作为 GIN 操作符类。)用于设置此模式的符号在access/gin.h中定义。

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

extra_data是一个输出参数,允许extractQuery将附加数据传递给consistentcomparePartial方法。要使用它,extractQuery必须分配一个包含*nkeys个指针的数组,并将其地址存储在*extra_data中,然后将它想要的任何内容存储到各个指针中。该变量在调用前初始化为NULL,因此不需要额外数据的操作符类可以简单地忽略此参数。如果设置了*extra_data,则整个数组将传递给consistent方法,并将相应的元素传递给comparePartial方法。

操作符类还必须提供一个函数来检查索引项是否与查询匹配。它有两种形式,一个布尔型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,与之前为该query数据项由extractQuery返回的键数相同。如果索引项包含相应的查询键,则check数组的每个元素都为 true,即如果 (check[i] == true) 则extractQuery结果数组的第 i 个键存在于索引项中。原始query数据项被传入,以防consistent方法需要查阅它,同时传入的还有之前由extractQuery返回的queryKeys[]nullFlags[]数组。extra_dataextractQuery返回的附加数据数组,如果没有则为NULL

extractQueryqueryKeys[]中返回空键时,如果索引项包含空键,则相应的check[]元素为真;也就是说,check[]的语义类似于IS NOT DISTINCT FROM。如果需要区分常规值匹配和空匹配,consistent函数可以检查相应的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[])

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

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

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

int compare(Datum a, Datum b)

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

或者,如果操作符类不提供compare方法,GIN 将查找索引键数据类型的默认 B 树操作符类,并使用其比较函数。建议在仅用于一种数据类型的 GIN 操作符类中指定比较函数,因为查找 B 树操作符类会花费一些周期。然而,多态 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参数。有关详细信息,请参阅第 65.4.4.2 节

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

65.4.4。实现 #

在内部,一个GIN索引包含一个在键上构建的 B 树索引,其中每个键是一个或多个索引项的元素(例如,数组的成员),并且叶页面中的每个元组都包含一个指向堆指针 B 树的指针(“发布树”),或者当列表足够小以适应单个索引元组和键值时,它包含一个简单的堆指针列表(“发布列表”)。图 65.1说明了 GIN 索引的这些组成部分。

PostgreSQL 9.1 开始,索引中可以包含空键值。此外,对于根据extractValue为空或不包含任何键的索引项,索引中还包含占位符空值。这允许搜索应该找到空项。

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

图 65.1。GIN 内部结构


65.4.4.1。GIN 快速更新技术 #

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

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

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

65.4.4.2。部分匹配算法 #

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

65.4.5。GIN 技巧和窍门 #

创建 vs. 插入

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

当为GIN启用fastupdate时(有关详细信息,请参阅第 65.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_pending_list_limit可以通过更改存储参数来覆盖单个 GIN 索引,这允许每个 GIN 索引拥有自己的清理阈值。例如,可以仅为可能被大量更新的 GIN 索引增加阈值,并否则降低它。

gin_fuzzy_search_limit

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

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

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

根据经验,数千个值(例如,5000 — 20000)效果很好。

65.4.6。限制 #

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

65.4.7。示例 #

核心 PostgreSQL 发行版包含GIN操作符类如表 65.3所示。以下contrib模块也包含GIN操作符类

btree_gin

B 树等效功能,适用于多种数据类型

hstore

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

intarray

int[]的增强支持

pg_trgm

使用三元组匹配进行文本相似度

提交更正

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