不能每次查询都调用to_tsvector,因为postgresql不会对表达式自动建索引,导致每次执行都要实时分词、生成向量并全表扫描,丧失全文检索加速能力,响应时间从毫秒级升至秒级。

直接用 to_tsvector 和 to_tsquery 拼接查询,不加索引、不持久化、不设权重,99% 的人会掉进性能陷阱——哪怕只有几万行数据,@@ 查询也会秒变全表扫描。
为什么不能每次查询都调用 to_tsvector
PostgreSQL 不会对表达式(如 to_tsvector('english', title || ' ' || content))自动建索引。每次执行 WHERE to_tsvector(...) @@ to_tsquery(...),数据库都得实时分词、标准化、生成向量,再逐行比对。这等于放弃所有全文检索的加速机制。
- 即使表只有 10 万行,响应时间也可能从毫秒级跳到秒级
-
EXPLAIN会明确显示Seq Scan,而非Bitmap Heap Scan+Index Scan - 并发稍高时,CPU 在分词上打满,不是在查数据
必须先建 tsvector 列并用 GIN 索引
核心动作是把向量化过程从查询时移到写入时,让索引真正生效。
- 添加列:
ALTER TABLE articles ADD COLUMN tsv tsvector GENERATED ALWAYS AS (setweight(to_tsvector('english', title), 'A') || setweight(to_tsvector('english', content), 'B')) STORED; - 建索引:
CREATE INDEX idx_articles_tsv ON articles USING gin(tsv); - 查询就变成轻量级匹配:
SELECT * FROM articles WHERE tsv @@ to_tsquery('english', 'database & postgres');
注意:中文必须换词典(如 zhparser),'english' 对中文无效——它会把整段中文当一个“词”,完全无法分词。
plainto_tsquery 和 websearch_to_tsquery 怎么选
二者都简化用户输入,但行为差异大,错用会导致漏结果或语法报错。
-
plainto_tsquery('english', 'hello world')→'hello' & 'world'(强制 AND,无标点解析) -
websearch_to_tsquery('english', '"hello world"')→ 精确短语匹配(PostgreSQL 11+) -
websearch_to_tsquery('english', 'hello -world')→ 包含 hello 但排除 world - 如果用户输入带引号或减号,必须用
websearch_to_tsquery,否则plainto_tsquery会把引号当普通字符,查不到东西
排序和相关性得分别忘了加 ts_rank
只用 @@ 匹配,结果是无序的——哪怕文档里关键词出现 10 次和 1 次,排在同一个位置。真实场景中,必须排序。
- 基础排序:
ORDER BY ts_rank(tsv, to_tsquery('english', 'search')) DESC - 更准一点(考虑词频+位置):
ts_rank_cd(tsv, to_tsquery('english', 'search')) - 千万避免写成
ORDER BY ts_rank(to_tsvector(...), ...)——又触发实时分词,排序本身变慢十倍
权重设置(A/B/C/D)和 ts_rank 是绑定的:没用 setweight,标题和正文就无法差异化贡献得分。










