bigywater.tech
PostgreSQL · 8

PostgreSQL 索引类型怎么选:B-tree、Hash、GIN、BRIN 在 100 万行上的大小、建索引耗时和查询耗时

“PostgreSQL 该选 B-tree、Hash、GIN 还是 BRIN” 是建索引时最常见的选择题。这篇在本机自己起一个 PostgreSQL 16.15,在同一张 100 万行的表上逐种建索引,量大小、建索引耗时和查询耗时,并验证每种索引的适用条件。

24 KB
顺序列 id 上的 BRIN 索引大小(B-tree 是 21960 KB),范围查询 0.44 ms
32.0 MB
Hash 索引大小:比 B-tree 的 18.8 MB 大,等值查询并不更快
59.80 → 0.07 ms
name like '%qzx%' 建 pg_trgm GIN 索引前后的耗时

等值查询

实测 · 本机执行输出
  没有索引:Seq Scan on t,39.88 ms
  btree:大小 18.8 MB,建索引 0.21 s;Index Scan using t_x on t,0.00 ms
  hash :大小 32.0 MB,建索引 0.88 s;Index Scan using t_x on t,0.01 ms
  brin :大小 24 KB,建索引 0.06 s;Bitmap Heap Scan on t,39.77 ms

B-tree 和 Hash 都是亚毫秒,Hash 更大;BRIN 在随机列上等于没有索引。

范围查询

实测 · 本机执行输出
  r 上的 btree索引(18.8 MB):Bitmap Heap Scan on t,0.40 ms
  r 上的 brin 索引(24 KB):Bitmap Heap Scan on t,43.66 ms
  id 上的 btree索引(21.4 MB):Index Scan using t_x on t,0.08 ms
  id 上的 brin 索引(24 KB):Bitmap Heap Scan on t,0.44 ms
  r 上只有 hash 索引:Seq Scan on t,39.97 ms(Hash 用不上范围查询,退回全表扫描)

Hash 不支持范围;BRIN 只在顺序列上有效,而且小得多。

BRIN 参数

实测 · 本机执行输出
  pages_per_range = 128 :索引 24 KB,Bitmap Heap Scan on t,0.40 ms
  pages_per_range = 32  :索引 32 KB,Bitmap Heap Scan on t,0.15 ms
  pages_per_range = 8   :索引 64 KB,Bitmap Heap Scan on t,0.22 ms
  pages_per_range = 1   :索引 352 KB,Bitmap Heap Scan on t,0.87 ms
  对照:B-tree 索引 21960 KB

pages_per_range 越小索引越大,并不一定更快;默认 128 通常够用。

GIN 与 jsonb

实测 · 本机执行输出
  没有索引:Seq Scan on t,101.30 ms
  using gin (doc)                   :大小 12.0 MB,建索引 1.31 s,Bitmap Heap Scan on t,5.78 ms
  using gin (doc jsonb_path_ops)    :大小 8.9 MB,建索引 0.49 s,Bitmap Heap Scan on t,2.17 ms
  额外插入 2 万行:没有 GIN 索引 0.02 s,有 GIN 索引 0.06 s

GIN 加速 @> 包含查询;jsonb_path_ops 更小更快但支持的操作符少;写入有额外代价。

pg_trgm

实测 · 本机执行输出
  没有索引:Seq Scan on t,59.80 ms,命中 192 行
  只有 B-tree 索引:Seq Scan on t,58.45 ms(B-tree 帮不上中间匹配)
  GIN + gin_trgm_ops:Bitmap Heap Scan on t,0.07 ms;索引 38.9 MB,建索引 2.12 s

trigram 的 GIN 索引让 LIKE '%abc%' 走索引,B-tree 做不到,代价是索引很大。

分步演示

索引类型对比
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / PostgreSQL 16.15(Homebrew);每个实验自己 initdb 一个数据目录,端口 9261 起,只监听 127.0.0.1,不碰本机已有的 5432 上的实例;命令行工具统一用 LC_ALL=C 取英文原文,路径换成 ;计时用 time.perf_counter 或 EXPLAIN ANALYZE 的 Execution Time,数字每次运行会有小幅抖动;实验结束后整个数据目录删除。关掉并行查询,shared_buffers = 512MB,100 万行的表全在内存里;查询耗时是 EXPLAIN ANALYZE 执行 5 次取最小的 Execution Time;建索引耗时只测一次,r、doc、name 列是 setseed 之后随机生成的
    • 没有验证:GiST、SP-GiST、Bloom 索引;全文检索(tsvector)的 GIN;多列索引的列顺序;冷缓存下的磁盘 IO;并发写入下的 GIN 的 fastupdate 挂起列表;索引的 fillfactor 与 B-tree 去重;PostgreSQL 其他大版本
    京ICP备2026067502号-1京公网安备11010502063487号