概述
col ILIKE '%term%' 会让 PostgreSQL 检查每一行,因为 B-tree 索引按前缀有序,前导通配符无法利用。大表上的搜索框因此成为系统里最贵的查询,而且内容越多越慢。全文检索换了一种思路:把文本归一化为词元,存入 tsvector,再用 GIN 索引,使匹配开销与命中数量成正比而不是与表大小成正比。
步骤
- 增加生成式 tsvector 列,并给标题更高权重:
ALTER TABLE articles
ADD COLUMN search_document tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(body, '')), 'B')
) STORED;
- 建立 GIN 索引:
CREATE INDEX articles_search_idx ON articles USING gin (search_document); - 使用
websearch_to_tsquery接受用户输入,它支持引号短语、or和前置减号,不会因标点报语法错误。 - 用
ts_rank排序并限制返回条数,再用ts_headline只对当前页生成摘要。
验证
查询 postgres index tuning 应返回按相关度排序的前 20 条,EXPLAIN (ANALYZE, BUFFERS) 中应出现 Bitmap Index Scan on articles_search_idx,buffer 读取量与命中数相关而不是与表大小相关。摘要查询应只返回一个带 <mark> 标签的片段。
注意事项
- 全文检索匹配词元而非子串,
post不会命中postgres,需要前缀查询或pg_trgm支持拼写容错。 english配置会做词干化,代码标识符建议用simple配置单独索引,再用 OR 合并查询。- 添加 STORED 生成列会重写整张表,大表需安排在维护窗口或改为分批回填加触发器维护。
ts_headline需要重新解析文档,开销较高,只对返回页生成,不要对所有命中行调用。- 生成列会增加写入成本,多语言混排内容需要按行选择配置或拆分列。