Skip to content

索引类型与结构

PostgreSQL 把索引抽象为 Access Method,并通过 Operator Class 定义某种数据类型的哪些操作符能够使用该索引。选择索引不能只看列类型,还要看查询谓词、操作符、数据分布和写入成本。


内置访问方法

类型结构与定位适合的访问
B-tree有序平衡树,默认类型等值、范围、排序、前缀模式匹配
Hash保存键的哈希码单列等值比较
GiST平衡搜索树框架,由 Operator Class 定义语义几何、范围、最近邻和可扩展搜索
SP-GiST空间分区搜索树框架四叉树、k-d tree、Trie 等非平衡数据结构
GIN倒排索引,键映射到包含它的行集合数组、JSONB、全文词项、多值包含查询
BRIN汇总连续 Heap Block Range 的值域超大表、列值与物理顺序高度相关的数据

GiST、SP-GiST 和 GIN 是可扩展框架,同一访问方法在不同 Operator Class 下支持的操作符不同。看到索引类型名称不能直接推断某个谓词一定可用。


B-tree 与 Heap

PostgreSQL 普通表是 Heap,B-tree 叶子项保存索引键和指向 Heap Tuple 的 TID。表数据不会像聚集索引组织表那样直接存放在主键索引叶子中,因此通过普通索引取完整行通常还要访问 Heap。

text
B-tree Root / Internal Page
→ Leaf Entry(Key + TID)
→ Heap Page
→ Tuple Version

表的物理顺序可以通过 CLUSTER 按某索引一次性重排,但后续写入不会自动持续维持这种顺序。它与始终按聚集键组织数据的聚集索引不是同一机制。


Index-Only Scan

INCLUDE 可以把仅用于返回、不用于搜索的列放入支持覆盖列的索引。即使查询列全部位于索引,PostgreSQL 仍需确认 Heap Tuple 对当前快照可见;只有 Visibility Map 表明对应 Heap Page 为 all-visible 时,才能跳过 Heap 访问。

覆盖索引的收益取决于查询列、页面可见性、VACUUM 维护速度以及索引变宽后的写入成本。高频更新表的 Visibility Map 会不断被清除,Index-Only Scan 仍可能产生大量 Heap Fetch。


组合、表达式与部分索引

形式作用关键边界
多列索引对组合条件、排序提供有序访问B-tree 通常最依赖左侧列形成的约束;列顺序需按真实谓词设计
表达式索引索引函数或表达式结果查询表达式需匹配定义,函数必须满足不可变性要求
部分索引只索引满足谓词的行优化器必须能证明查询条件蕴含索引谓词
唯一索引强制键值唯一唯一约束通常由唯一 B-tree 实现;NULL 语义需按定义确认
INCLUDE保存只用于返回的覆盖列覆盖列不参与搜索,会增加索引体积

优化器也可以组合多个索引生成 Bitmap Scan。多个单列索引有时能组合过滤,但会丢失原有索引顺序,也不一定优于专门的复合索引。


特殊访问方法的选择

问题常见选择原因
JSONB 包含某个键值GIN把复合值拆成检索键并维护 Posting List
全文包含词项GIN适合词项到文档集合的倒排访问
几何相交或最近邻GiST / SP-GiST由空间 Operator Class 提供重叠、距离等语义
时间递增的超大日志表范围查询BRIN索引极小,可按块范围排除数据
更新频繁的小型等值表B-tree通用、选择性定位稳定

BRIN 返回的是可能匹配的块范围,需要 Recheck,不适合物理顺序和列值无相关性的随机数据。GIN 写入和维护成本通常高于 B-tree,不能因查询快就忽略写路径。


维护判断

  • EXPLAIN (ANALYZE, BUFFERS) 区分估算问题、访问路径问题和真实 I/O。
  • 统计信息不准确时,合理索引也可能不被选择。
  • 长事务与 VACUUM 积压会使 Heap 和索引保留无效条目。
  • 并发重建仍会消耗 CPU、I/O、WAL 和额外磁盘空间。
  • 删除重复索引前检查约束依赖、写入成本和完整业务周期内的使用情况。

顺序扫描不一定是异常。当查询需要表中较大比例的数据时,连续扫描可能比大量随机索引访问更便宜。

参考资料