You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过PostgreSQL快速查询相似文本?求高效优化方案

相似文本查询性能优化方案

问题背景

需求为根据输入内容查询最相似的前N条文本,当前使用的documents表结构及索引如下:

create table documents (
    id                                 bigserial primary key,
    content                            text,
    content_similarity_tokens          text,
    content_index_tokens               text,
    index_tokens_vector tsvector generated always as (to_tsvector('simple', content_index_tokens)) stored
);

create index search_index_tokens_vector  on documents using gin (index_tokens_vector);

字段说明:

  • content: 原始文本内容
  • content_similarity_tokens: 去除标点后的文本
  • content_index_tokens: 分词后的文本
  • index_tokens_vector: 存储分词文本的向量,用于全文检索

目前基于index_tokens_vector的全文检索效果正常,示例SQL:

select * from documents where index_tokens_vector @@ plainto_tsquery('用户输入的分词文本');

但相似文本查询存在以下问题:

  1. 直接调用similarity函数在数据量大时会触发全表扫描,查询极慢:
select id, content, similarity(content, '用户输入文本') as sim
from documents
where similarity(content, '用户输入文本') > 0.7
order by sim desc;
  1. 先通过全文检索过滤再计算相似度的方式仍有缺陷:
    • 过滤后结果集较大时,计算速度依旧缓慢;
    • 会误判高相似度文本,例如库中text1: '我今年18岁'分词为'18 岁',用户输入text2: '我今年19岁'分词为'19 岁',因'19'不在text1分词中被过滤,但两者实际相似度达0.8:
select similarity('我今年18岁', '我今年19岁');
-- 返回相似度: 0.8

优化方案

一、用PG_TRGM扩展+索引加速similarity查询

PostgreSQL的pg_trgm扩展专为文本相似度匹配设计,能为similarity函数提供索引支持,彻底避免全表扫描。

操作步骤:

  1. 启用pg_trgm扩展:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
  1. 为相似度计算字段创建GIN/GIST索引(GIN适合高选择性场景,速度更快;GIST占用空间更小):
    推荐使用已去标点的content_similarity_tokens字段:
-- GIN索引(优先选择)
CREATE INDEX idx_documents_similarity_trgm ON documents USING GIN (content_similarity_tokens gin_trgm_ops);

-- 或GIST索引(存储空间有限时选择)
CREATE INDEX idx_documents_similarity_trgm ON documents USING GIST (content_similarity_tokens gist_trgm_ops);
  1. 优化后的查询语句:
    直接用%操作符匹配(默认阈值0.3,可结合similarity自定义阈值):
SELECT id, content, similarity(content_similarity_tokens, '用户输入的去标点文本') AS sim
FROM documents
WHERE content_similarity_tokens % '用户输入的去标点文本'
ORDER BY sim DESC
LIMIT 10; -- 限制返回条数,进一步提速

如果需要自定义相似度阈值(比如0.7):

SELECT id, content, similarity(content_similarity_tokens, '用户输入的去标点文本') AS sim
FROM documents
WHERE similarity(content_similarity_tokens, '用户输入的去标点文本') > 0.7
ORDER BY sim DESC
LIMIT 10;

此时查询会通过trgm索引快速过滤符合条件的记录,无需全表遍历。

二、调整分词策略,避免高相似度文本被误过滤

针对分词导致的漏判问题,优化content_index_tokens的生成逻辑:

  • 对数字、日期这类易变化但语义相似的内容做归一化处理,比如把所有数字替换为占位符[NUM],这样'我今年18岁'和'我今年19岁'的分词都会变成'我 今年 [NUM] 岁',全文检索时就能命中;
  • 换用更智能的分词器(比如中文用zhparser扩展,英文支持更多上下文保留的分词规则),避免仅因单个token不匹配就排除整个文本。

三、混合全文检索与trgm相似度的互补查询

如果需要兼顾全文检索的精准性和相似度的召回率,可以用以下方式优化:

  1. 对用户输入文本进行扩展处理,比如提取同义词、数字范围等,扩大全文检索的候选集;
  2. 对候选集用trgm索引计算相似度排序,既缩小计算范围,又不会漏判高相似度文本:
WITH candidates AS (
    SELECT id, content, content_similarity_tokens
    FROM documents
    -- 扩展查询条件,比如加入数字相关的模糊匹配或同义词
    WHERE index_tokens_vector @@ plainto_tsquery('用户输入的分词文本 | 相关扩展token')
)
SELECT id, content, similarity(content_similarity_tokens, '用户输入的去标点文本') AS sim
FROM candidates
ORDER BY sim DESC
LIMIT 10;

四、预计算语义向量(适合静态/低更新数据)

如果数据更新不频繁,可通过预计算文本嵌入向量实现高速语义相似度查询,需用到pgvector扩展:

  1. 添加向量字段:
ALTER TABLE documents ADD COLUMN content_vector vector(300); -- 向量维度根据模型调整,比如300/768
  1. 批量生成并插入嵌入向量(需外部模型生成,比如Word2Vec、BERT):
-- 示例:假设已有生成嵌入向量的函数generate_embedding
UPDATE documents SET content_vector = generate_embedding(content);
  1. 创建向量索引:
CREATE INDEX idx_documents_content_vector ON documents USING ivfflat (content_vector vector_cosine_ops);
  1. 查询语句(余弦相似度):
SELECT id, content, 1 - (content_vector <=> generate_embedding('用户输入文本')) AS sim
FROM documents
ORDER BY sim DESC
LIMIT 10;

这种方式查询速度极快,且能识别语义相似性,但需要额外的向量生成和维护成本。


内容的提问来源于stack exchange,提问作者accbear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 10:14:57