这个坑值得记一笔。Postgres 16 加 pgvector 0.8.0,HNSW 索引,查询带 tenant_id 过滤且 limit 20,实际只回 3 条,把过滤条件去掉能回满,ef_search 已经调到 200。

刚开始盯着「过滤列没有索引,走的是全表扫描再排序」,折腾了半个多小时,排掉了。

原因不止一个,得分开看。HNSW 先取 ef_search 个候选再过滤,选择性高时被过滤光;pgvector 0.8 的 iterative_scan 默认是关闭的;连接池复用了旧会话,SET 的参数没有生效。

动手时先看这条查询的执行计划

psql -c "EXPLAIN ANALYZE SELECT id FROM docs ORDER BY vec <-> '[0.1,0.2]' LIMIT 20;"

再往下开启迭代扫描并放宽扫描上限

psql -c "SET hnsw.iterative_scan = relaxed_order; SET hnsw.max_scan_tuples = 20000;"

给过滤列补一个普通索引

psql -c "CREATE INDEX IF NOT EXISTS idx_docs_tenant ON docs (tenant_id);"

剩下的就是重跑同一查询数返回条数

psql -c "SELECT count(*) FROM (SELECT id FROM docs WHERE tenant_id=42 ORDER BY vec <-> '[0.1,0.2]' LIMIT 20) t;"

命令输出我都留了记录,免得改完记不清原来的值。

改完先别撤,确认这几项。带过滤的查询稳定返回 20 条,执行计划里出现 iterative scan 字样,P95 延迟在 40 毫秒以内。

提醒一点。过滤选择性越高越要开迭代扫描,否则调大 ef_search 只是让扫描更慢,返回条数还是不够。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189