首先是创建表以及建立索引
CREATE TABLE document_meta (
id SERIAL PRIMARY KEY,
title VARCHAR(512), -- 文档标题
content TEXT, -- 文档正文
ts TSVECTOR -- 用于关键词搜索
);
CREATE INDEX idx_meta_tsvector
ON document_meta USING gin (ts);
CREATE TABLE document_vectors (
id SERIAL PRIMARY KEY,
meta_id INT NOT NULL REFERENCES document_meta(id) ON DELETE CASCADE, -- 关联主表结果
embedding VECTOR(1024) NOT NULL -- 向量字段
);
CREATE INDEX idx_docvec_embedding
ON document_vectors USING ivfflat (embedding vector_l2_ops)
WITH (lists = 100);
首先是关键词搜索
SELECT * FROM document_meta
WHERE ts @@ to_tsquery('simple', #{tsQuery})
ORDER BY ts_rank_cd(ts, to_tsquery('simple', '公园')) DESC
这个原理大家都懂,先是通过分词器对文档进行分词,然后存入tsvector中,然后再用关键词进行检索,排序的含义是关键词命中越多越前面.
tsvector 是 PostgreSQL 的一种专门用于全文检索的数据类型。
它其实是一个结构化的数据,类似于这样子
'一起':6 '今天天气':1 '公园':8 '很好':3 '我们':5
每个词被存储为词干,后面数字是该词在文档中出现的位置。
底层实现是 GIN(Generalized Inverted Index)是倒排索引的一种实现方式,非常适合做关键词搜索。
索引结构
词项 (lexeme) 出现在哪些文档
"天气" [doc1, doc7, doc12]
"公园" [doc1, doc4]
"我们" [doc1, doc5, doc9]
也就是一个“词 → 文档ID列表”的映射。
当你执行 WHERE ts @@ to_tsquery('公园') 时,Postgres 直接查索引里“公园”对应的文档ID集合,最后返回的结果集是所有包含这词的文档.
然后是向量搜索
SELECT m.*
FROM document_meta m
JOIN document_vectors v ON m.id = v.meta_id
ORDER BY (v.embedding <-> [0.12, 0.33, ...])
向量搜索(Vector Search),也叫语义搜索(Semantic Search),是现代信息检索中用来理解“相似度”的关键技术。它的核心原理是:将文本、图片、音频等信息转化为高维向量(embedding),再通过数学方法计算这些向量之间的相似度,从而找到语义上最接近的结果。
上面的关键词匹配是字面匹配,搜“我想吃苹果”,不会返回“我想吃水果”。
而向量搜索是语义匹配:模型将“苹果”和“水果”编码成相似的向量,使搜索系统理解“这两者在语义上相关”。
当我们要搜索一个 query 时,比如“我想吃苹果”,步骤如下:
对 query 生成向量:v_query
数据库中每个文档也有预存的向量:v_doc_i
然后计算他们的相似度,越相似的向量 → 距离越小 → 越相关。
文本/图片 → 向量模型(Embedding) → 存入向量数据库
↑ ↓
查询文本 → 向量化 → 相似度计算 → 返回最相似的结果
自认为一个比较不错的实践方案
SELECT
m.*,
(0.7 * (1 - (v.embedding <-> [0.12, 0.33, ...])) + 0.3 * ts_rank_cd(m.ts, to_tsquery('simple', '天气'))) AS hybrid_score
FROM document_meta m
JOIN document_vectors v ON m.id = v.meta_id
WHERE m.ts @@ to_tsquery('simple', '天气')
and (v.embedding <-> [0.12, 0.33, ...]) < 25
ORDER BY hybrid_score DESC
先用关键词匹配,匹配出的结果再按语义接近程度来排序
向量距离限制在25(非归一化,各个模型不同),一般距离太大了之后那说明这两句话语义已经基本没什么关联了.如果关键词没匹配到结果再使用向量来补充.这样就能得到比较满意的结果了.
小贴士: 分词器和向量模型一旦确定下来就不要经常换,更换后要及时重新生成. 用户输入的关键词也要用同样的步骤来处理

