PostgreSQL 提供了好几种索引类型:B-tree, Hash, GiST, GIN 。每种索引类型都比较适合某些特定的查询类型,因为它们用了不同的算法。缺省时,CREATE INDEX 命令将创建一个 B-tree 索引,它适合大多数情况。
B-tree 适合处理那些能够按顺序存储的数据之上的等于和范围查询。特别是在一个建立了索引的字段涉及到使用
< |
<= |
= |
>= |
> |
操作符之一进行比较的时候,PostgreSQL 的查询规划器都会考虑使用 B-tree 索引。等效于这些操作符组合的构造,比如 BETWEEN 和 IN ,也可以用搜索 B-tree 索引实现。但是要注意,IS NULL 不同于 = 并且是不能建立索引的。
仅当模式是一个常量,并且锚定在字符串开头的时候,优化器才会把 B-tree 索引用于模式匹配操作符 LIKE 和 ~ ,比如:col LIKE 'foo%' 或 col ~ '^foo' (这里的~和like差不多,差别在于~后面放的正则,^表示匹配输入字符串的开始位置),但是 col LIKE '%bar' 就不行。同时,如果你的服务器未使用 C 区域设置,那么你需要用一个特殊的操作符类创建索引来支持模式匹配查询上的索引。参阅节11.8。还有可能将 B-tree 索引用于 ILIKE 和 ~* ,但是仅当模式以非字母字符(不受大小写影响的字符)开头才可以。
Hash 索引只能处理简单的等于比较。当一个索引了的列涉及到使用 = 操作符进行比较的时候,查询规划器会考虑使用 Hash 索引。下面的命令用于创建 Hash 索引:
CREATE INDEX name ON table USING hash (column);
【注意】测试表明,PostgreSQL 的 Hash 索引的性能不比 B-tree 索引强,而 Hash 索引的尺寸和制作时间更差。另外,Hash 索引操作目前没有记录 WAL 日志,因此如果发生了数据库崩溃,我们可能需要用 REINDEX 重建 Hash 索引。因为这些原因,我们并不鼓励使用 Hash 索引(官放不建议使用还是可以的)。
GiST 的意思是通用的搜索树(Generalized Search Tree)。。它是一种平衡树结构的访问方法,在系统中起一个基础的模版,然后可以使用它实现任意索引模式。B-trees 和许多其它的索引模式都可以用 GiST 实现。
GiST 的一个优点是它允许一种自定义的数据类型和合适的访问方法一起开发,并且是由该数据类型范畴里的专家,而不是数据库专家开发。
GiST 索引不是单独一种索引类型,而是一种架构,可以在这种架构上实现很多不同的索引策略。因此,可以使用 GiST 索引的特定操作符类型高度依赖于索引策略(操作符类)。作为示例,PostgreSQL 的标准发布中包含用于二维几何数据类型的 GiST 操作符类,它支持
下面的几何操作符:看起来复杂,但是一旦用到确实必不可少,知道哪里查就行了。
<< | |
是否严格在左? | circle '((0,0),1)' << circle '((5,0),1)' |
&< | |
是否没有延伸到右边? | box '((0,0),(1,1))' &< box '((0,0),(2,2))' |
&> | |
是否没有延伸到左边? | box '((0,0),(3,3))' &> box '((0,0),(2,2))' |
>> | |
是否严格在右? | circle '((5,0),1)' >> circle '((0,0),1)' |
<<| | |
严格在下? | box '((0,0),(3,3))' <<| box '((3,4),(5,5))' |
&<| | |
严格在上? | box '((3,4),(5,5))' |>> box '((0,0),(3,3))' |
|&> | |
没有延伸到下面? | box '((0,0),(3,3))' |&> box '((0,0),(2,2))' |
|>> | |
严格在上? | box '((3,4),(5,5))' |>> box '((0,0),(3,3))' |
@> | |
包含? | circle '((0,0),2)' @> point '(1,1)' |
<@ | |
包含或在...上? | point '(1,1)' <@ circle '((0,0),2)' |
~= | |
与...相同? | polygon '((0,0),(1,1))' ~= polygon '((1,1),(0,0))' |
&& | |
重叠? | box '((0,0),(1,1))' && box '((0,0),(2,2))' |
操作符的索引查询。这些操作符的含义参见节9.10。许多其它 GiST 操作符类位于 contrib 中,或者是单独的项目,更多信息参见章50。
通常,实现一种新的索引访问方法意味着大量的艰苦工作。必须理解数据库的内部工作机制,比如锁的机制和预写日志。GiST 接口有一个高层的抽像,只要求访问方法的实现者实现被访问的数据类型的语意。GiST 层本身会处理并发,日志和搜索树结构的任务。
不要把这个扩展性和其它标准搜索树的扩展性混淆在一起,比如它们所能处理的数据等方面。比如,PostgreSQL 支持可以扩展的 B-trees 。这就意味着呢可以用 PostgreSQL 在任意你需要的数据类型上建立 B-tree 。但是 B-trees 只支持范围谓词(<, =, >),而 hash 仅支持相等查询。
所以,如果你用PostgreSQL B-tree 索引了一个图像集,那么你就只能发出类似"图像 x 和图像 y 相等吗"、"图像 x 是不是比图像 y 小"、"图像 x 是否大于图像 y"?这样的查询。根据你在这个环境下定义的"等于"、"小于"、"大于"的含义,上面这些查询可能有意义。但是,使用一个基于 GiST 的索引,你可以创建一些方法来发出和域相关的问题,比如"找出所有马的图像"或者"找出所有曝光过头的图像"。
要让一种 GiST 访问模式跑起来的方法只是实现七个用户定义的方法,这七个方法定义了树里面的键字的行为。当然,为了支持那些怪异的查询,这些方法也会相当怪异,但是对于所有标准的查询(B-trees 等),他们是相当直接的。简单说,GiST 组合了扩展性和通用性,以及代码复用和一个干净的界面。
一个用于 GiST 的索引操作符类必须提供的七个方法:
consistent
给出一个在树的数据页上的谓词 p 和一个用户查询 q ,如果对于一个给定的数据项,p 和 q 都很明确地不能为真,那么这个方法将返回假。
union
这个方法合并树中的信息。给出一个条目的集合,这个函数生成一个新的谓词,这个谓词对所有这些条目都为真。
compress
将数据项转换成一个适合于在一个索引页里面物理存储的格式。
decompress
compress
方法的反方法。把一个数据项的索引表现形式转换成可以由数据库操作的格式。
penalty
返回一个表示将新条目插入树中特定分支需要的"开销"的数值。项将会按照树中最小 penalty
的路径插下去。
picksplit
如果需要分裂一个页面的时候,这个函数决定页面中哪些条目保存呆旧页面里,而哪些移动到新页面里。
same
如果两个条目相同,返回真,否则返回假。
PostgreSQL 源代码版本包括好几个使用 GiST 实现的索引方法。核心系统目前为某些内置的几何数据类型提供 R-Tree 的等效功能(参阅 src/backend/access/gist/gistproc.c)。下面的 contrib 模块也包含 GiST 操作符类:
btree_gist
好几种类型的 B-Tree 等效
cube
用于多维立方体的索引
intarray
用于一维 int4 数组值的 RD-Tree
ltree
用于树状结构的索引
pg_trgm
使用 trigram 匹配的文本相似查找
seg
用于"漂浮范围"(float ranges)的索引
tsearch2
全文索引
通常,重放 WAL 日志就足以再数据库崩溃之后恢复 GiST 索引的完整性。不过,还存在一些边角的情况,这些时候索引状态无法完整地重建。这时候索引从作用上仍然是正确的,但是可能会导致一些性能的降低。在发生这种情况的时候,索引可以通过 VACUUM 其所有表来修复,或者通过使用 REINDEX 重建索引来修复。在某些情况下,单纯的 VACUUM 是不够的,需要 VACUUM FULL 或 REINDEX 。是否需要这些步骤,可以从崩溃恢复的日志信息中得到提示:
LOG: index NNN/NNN/NNN needs VACUUM or REINDEX to finish crash recovery
或者在索引插入的时候出现下面的日志信息:
LOG: index "FOO" needs VACUUM or REINDEX to finish crash recovery
如果一个单纯的 VACUUM 觉得自己无法完整地恢复,它会返回一个提示:
NOTICE: index "FOO" needs VACUUM FULL or REINDEX to finish crash recovery
GIN 的意思是基因倒排索引(Generalized Inverted Index)。它是一个存储(key, posting list)对集合的索引结构,这里的"posting list"是一组出现 key 的行。每一个被索引的值都可能包含多个 key ,因此同一个行 ID 可能会出现在多个 posting list 中。
在一般意义上,GIN 索引不需要关心相关联的操作。相反,它使用用户在特定数据类型上定义的策略。
GIN 的一个优点是它允许开发自定义数据类型时附带适当的访问方法,这件事可以由深入了解该数据类型的专家来做,而不是由数据库专家来做。这一点与使用 GiST 很相似。
PostgreSQL 中的 GIN 实现主要由 Teodor Sigaev 和 Oleg Bartunov 维护。关于 GIN 的更多信息可以访问他们的网站。
GIN 索引是反转索引,它可以处理包含多个键的值(比如数组)。与 GiST 类似,GIN 支持用户定义的索引策略,可以使用 GIN 索引的特定操作符类型根据索引策略的不同而不同。作为示例,PostgreSQL 的标准发布中包含用于一维数组的 GIN 操作符类,它支持
<@ |
@> |
= |
&& |
操作符的索引查询。这些操作符的含义参见节9.14。许多其它 GIN 操作符类位于 contrib, tsearch2, intarray 模块。更多信息参见章51。
GIN 接口有一个高层次的抽象,仅要求实现被访问数据类型的语义即可。GIN 层自身可以处理并发操作、记录日志、搜索树结构。
定义一个 GIN 访问方法所要做的所有事情就是实现四个用户定义的方法,这些方法定义了 key 在树中的行为、key 与 key 之间的关系、被索引的值、能够使用索引的查询。简而言之,GIN 将扩展性与普遍性、代码重用、清晰的接口结合在了一起。
一个 GIN 索引操作符类必须实现的四个方法如下:
int compare(Datum a, Datum b)
比较两个 key(不是被索引的值!)然后返回一个小于/等于/大于零的值,分别表示第一个 key 小于/等于/大于第二个 key
Datum* extractValue(Datum inputValue, uint32 *nkeys)
返回一个其值用于索引的 key 数组,数组中元素的个数存放在 *nkeys 中。
Datum* extractQuery(Datum query, uint32 *nkeys, StrategyNumber n)
返回一个其值用于查询的 key 数组。也就是说,query 是可索引操作符右侧的值,而该操作符左侧是被索引的字段。n 操作符类中操作符的策略号(参见 节33.14.2)。通常,extractQuery
用来通过考量 n 来决定 query 的数据类型以及需要提取的 key 值。数组中元素的个数存放在 *nkeys 中。
bool consistent(bool check[], StrategyNumber n, Datum query)
如果索引值满足查询策略号为 n 的操作符(或者如果该操作符在操作符类中被标记为 RECHECK 也可能满足)则返回 TRUE 。check 数组的长度必须与先前由 extractQuery
为该查询返回的 key 的数量相同。如果索引值包含相应的查询 key ,那么check 数组中的每一个元素都是 TRUE ,也就是如果 check[i] == TRUE ,那么 extractQuery
结果数组的第 i 个 key 存在于索引值当中。在 consistent
方法需要查看它的情况下,原始的 query datum(不是抽取出的 key 数组!) 将会被传递。
在内部,一个 GIN 索引包含一个构建在 key 上的 B-tree 索引,每一个 key 已索引值(比如数组的一个元素)的一个元素,并且这里叶子页中的每一个元组要么是指向堆指针(PL, posting list)上的 B-tree 的指针,要么当列表足够小的时候是一个堆指针(PL, posting list)的列表。
创建 vs 插入
在大多数情况下,因为需要为每个值插入多个相似的 key 的原因,向 GIN 索引插入是慢速操作。对于向表中大量插入的操作,我们建议先删除 GIN 索引,在完成插入之后在重建它。
gin_fuzzy_search_limit(integer)(GIN 索引返回的集合尺寸软上限)
"软"的意识是实际返回的结果集大小可能与指定值稍有出入,具体取决于查询和系统的随机数发生器。
开发 GIN 索引的主要目的是为了让 PostgreSQL 支持高度可升级的全文索引,并且常常会遇见全文索引返回海量结果的情形。此外,在查询高频词的时候得到的结果集没什么用处。因为从磁盘读取大量记录并对其进行排序会消耗大量资源,这在产品环境下是不能接受的(注意,索引搜索本身是很快的)。
为了易于控制这种情况,GIN 有一个可配置的结果集大小软上限配置参数 gin_fuzzy_search_limit 。缺省值 0 表示没有限制。如果设置了非零值,那么返回的结果就是从完整结果集中随机选择的一部分。
GIN 不支持完整的索引扫描,因为通常每个值对应多个 key ,每个堆指针都会被多次返回,并且没有有效的办法防止这种情况的发生。
当 extractQuery
返回零个 key 的时候,GIN 将会报错。取决于不同的操作符,一个空白查询可能匹配所有或部分甚至不匹配任何索引值(例如每个数组都包含空数组,但是并不重叠)。GIN 既无法检查正确的结果,也无法生成全索引扫描的结果。
extractValue
返回零个 key 并不是错误,不过在这种情况下,该索引值将在索引中没有代表性。这也是为什么全索引扫描没什么用处的的另外一个原因:它同样会错过这些行。
GIN 仅使用相等匹配搜索 key 。这一点在将来或许可以被改进。
PostgreSQL 源代码发布中包含所有内置数据类型的一维数组的 GIN 类。下面的 contrib 模块也同样包含 GIN 操作符类:
intarray
对 int4[] 的增强支持
tsearch2
支持倒序文本索引。这对于处理非常大而且基本保持不变的文档非常迅速。