跳转到内容

5.4 索引选型与调优实战

选对索引只是第一步——调对参数才是让 pgvector 真正飞起来的关键


这一节在讲什么?

前面两节我们分别学了 IVFFlat 和 HNSW 的原理和参数,但"知道参数是什么"和"知道怎么调"是两回事。这一节我们要建立一套完整的索引选型和调优方法论——什么时候该建索引、选哪种索引、参数怎么设、如何用 EXPLAIN ANALYZE 诊断问题、以及距离操作符与索引的对应关系(这是最容易踩坑的地方)。


索引选型决策树

你的数据量有多大?

├─ < 10K 条
│   → 不需要向量索引,暴力搜索足够快

├─ 10K ~ 1M 条
│   │
│   ├─ 内存充足(> 4GB)?
│   │   ├─ 是 → HNSW(m=16, ef_construction=64)
│   │   └─ 否 → IVFFlat(lists=√N)
│   │
│   └─ 数据频繁更新?
│       ├─ 是 → HNSW(支持增量更新)
│       └─ 否 → HNSW 或 IVFFlat 都可以

└─ > 1M 条

    ├─ 内存充足(> 8GB)?
    │   ├─ 是 → HNSW(m=32, ef_construction=128)
    │   └─ 否 → IVFFlat(lists=√N)

    └─ 需要极致查询速度?
        ├─ 是 → HNSW(ef_search=100~200)
        └─ 否 → IVFFlat(probes=lists×5%)

距离操作符与索引的对应关系

这是 pgvector 调优中最容易踩的坑——索引创建时指定的 ops 必须与查询时使用的操作符一致,否则索引不会被使用:

索引 ops对应的操作符距离类型
vector_l2_ops<->L2 距离
vector_cosine_ops<=>余弦距离
vector_ip_ops<#>负内积
sql
-- 创建余弦距离索引
CREATE INDEX idx_emb_cosine ON documents
USING hnsw (embedding vector_cosine_ops);

-- ✅ 使用 <=> 操作符查询——索引生效
SELECT * FROM documents ORDER BY embedding <=> '[0.1, ...]' LIMIT 5;

-- ❌ 使用 <-> 操作符查询——索引不生效!走全表扫描
SELECT * FROM documents ORDER BY embedding <-> '[0.1, ...]' LIMIT 5;

如果你需要同时支持多种距离度量的查询,可以创建多个索引:

sql
-- 同时创建三种距离的索引
CREATE INDEX idx_emb_l2 ON documents USING hnsw (embedding vector_l2_ops);
CREATE INDEX idx_emb_cosine ON documents USING hnsw (embedding vector_cosine_ops);
CREATE INDEX idx_emb_ip ON documents USING hnsw (embedding vector_ip_ops);

但这会占用 3 倍的内存。更实际的做法是只创建最常用的距离度量索引(通常是 cosine),其他距离用暴力搜索。


EXPLAIN ANALYZE 诊断

sql
-- 检查索引是否被使用
EXPLAIN ANALYZE
SELECT id, content, embedding <=> '[0.1, ...]'::vector(384) AS distance
FROM documents
ORDER BY embedding <=> '[0.1, ...]'::vector(384)
LIMIT 5;

索引生效的输出:

Index Scan using idx_emb_cosine on documents
  Index Cond: (embedding <=> '[0.1, ...]'::vector(384))
  Execution Time: 2.345 ms

索引未生效(全表扫描)的输出:

Seq Scan on documents
  Sort Key: (embedding <=> '[0.1, ...]'::vector(384))
  Execution Time: 234.567 ms

如果看到 Seq Scan,检查以下几点:

  1. 操作符是否与索引 ops 匹配
  2. 数据量是否太小(PostgreSQL 可能判断全表扫描更快)
  3. 索引是否已构建完成

性能基准参考

以下是在不同数据量/维度/索引配置下的典型性能参考(单机,16GB 内存,SSD):

数据量维度索引查询延迟QPS召回率@10
10K384~20ms~50100%
10K384HNSW~1ms~100099%
100K384HNSW~3ms~30098%
1M384HNSW~8ms~12097%
1M384IVFFlat~15ms~6095%
1M1536HNSW~25ms~4097%

常见误区

误区 1:建了索引就一定快

PostgreSQL 的优化器会根据成本估算选择执行计划。如果它判断全表扫描更快(比如数据量很小或查询返回大量行),就不会使用索引。用 EXPLAIN ANALYZE 确认。

误区 2:一个索引支持所有距离度量

每种距离度量需要单独的索引。用 <=> 查询时只有 vector_cosine_ops 索引生效,vector_l2_ops 索引不会被使用。

误区 3:HNSW 的 ef_search 是全局设置

SET hnsw.ef_search = 100 只对当前数据库会话生效,不影响其他连接。你需要在每个查询会话中设置,或者在应用代码中每次连接后设置。


本章小结

索引选型和调优是 pgvector 性能优化的核心环节。核心要点回顾:第一,数据量 < 10K 不需要索引,> 1M 优先选 HNSW;第二,索引 ops 必须与查询操作符匹配——vector_cosine_ops 对应 <=>vector_l2_ops 对应 <->vector_ip_ops 对应 <#>;第三,用 EXPLAIN ANALYZE 确认索引是否被使用,看到 Seq Scan 说明索引未生效;第四,HNSW 的推荐参数起点是 m=16、ef_construction=64、ef_search=40;第五,ef_search 只对当前会话生效,需要在应用代码中设置。

下一章我们将进入 RAG 系统实战——用 pgvector 构建一个完整的文档问答系统。

基于 MIT 许可发布