PostgreSQL 全文搜索解决搜索结果不准确难题

时间:2025-02-13 15:41:58

PostgreSQL 全文搜索解决搜索结果不准确难题

一、“搜索灾难”降临

在当今这个信息爆炸的时代,全文搜索功能就像是一把万能钥匙,能帮我们在海量的数据中快速找到所需的信息。我们公司开发了一个知识管理系统,使用 PostgreSQL 作为数据库,并且利用它强大的全文搜索功能来实现文档搜索。

系统里有一个 documents 表,用来存储各种文档信息,表结构如下:

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    title TEXT,
    content TEXT,
    tsvector_column TSVECTOR
);

这里的 tsvector_column 是用于全文搜索的向量列,它存储了经过处理的文本信息,方便快速搜索。

我们使用 tsvectortsquery 来实现全文搜索。例如,当用户搜索“数据库优化”时,查询语句如下:

SELECT title
FROM documents
WHERE tsvector_column @@ to_tsquery('数据库优化');

@@ 是全文搜索的匹配操作符,to_tsquery 函数将搜索词转换为查询对象。

一开始,这个搜索功能运行得还算正常,就像一辆崭新的小汽车,稳稳地行驶在公路上。然而,随着文档数量的不断增加,问题逐渐浮现出来。用户反馈搜索结果不准确,有时候搜索“数据库优化”,结果却出现了很多和“数据库”或者“优化”相关但和“数据库优化”整体主题不相关的文档,就好像你想去超市买苹果,结果超市给你一堆香蕉、橘子,就是没有苹果,这可把用户们急坏了,也把我这个负责数据库的人搞得焦头烂额。

二、踏上排查“迷宫”之旅

我决定像个勇敢的探险家一样,深入到 PostgreSQL 这个神秘的“迷宫”中,找出搜索结果不准确的原因。

1. 分词规则的初步怀疑

我首先想到的是分词规则可能有问题。PostgreSQL 默认的分词规则是基于英语的,而我们的文档是中文,这就好比用一把英语的钥匙去开中文的锁,肯定会出问题。我查看了当前的分词配置:

SHOW default_text_search_config;

结果显示是 english,果然如此。我需要把分词配置改成适合中文的。我安装了 zhparser 中文分词器,然后创建了一个新的文本搜索配置:

CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;

接着,我更新 tsvector_column 列,使用新的分词配置:

UPDATE documents
SET tsvector_column = to_tsvector('chinese', title ||'' || content);

我满心期待地再次运行搜索查询:

SELECT title
FROM documents
WHERE tsvector_column @@ to_tsquery('chinese', '数据库优化');

然而,搜索结果并没有明显改善,还是乱七八糟的。我感觉自己就像在迷宫里走错了路,又回到了原点。

2. 搜索查询表达式的深度分析

我开始怀疑是不是搜索查询表达式有问题。to_tsquery 函数默认的操作符是 &(逻辑与),但它在处理中文时可能不够灵活。我尝试使用更复杂的查询表达式,比如使用 |(逻辑或)和 !(逻辑非)来组合搜索词。

例如,我想排除一些和“数据库”相关但和“优化”无关的文档,查询语句如下:

SELECT title
FROM documents
WHERE tsvector_column @@ to_tsquery('chinese', '数据库 & 优化 &!(数据库!优化)');

这查询语句写得我头晕眼花,就像在解一道超级复杂的数学题。但运行结果还是不尽人意,搜索结果还是不准确。我感觉自己就像一个在黑暗中摸索的盲人,怎么也找不到正确的方向。

3. 权重和排名的研究探索

我又想到,是不是因为没有给不同的字段设置权重和排名,导致搜索结果的排序不合理。我决定给 title 字段设置更高的权重,因为标题通常更能代表文档的主题。

我更新 tsvector_column 列,为不同字段设置权重:

UPDATE documents
SET tsvector_column = setweight(to_tsvector('chinese', title), 'A') ||
                      setweight(to_tsvector('chinese', content), 'B');

这里的 setweight 函数用来设置权重,A 表示最高权重,B 表示次高权重。

然后,我修改搜索查询,使用 ts_rank 函数来对搜索结果进行排名:

SELECT title, ts_rank(tsvector_column, to_tsquery('chinese', '数据库优化')) AS rank
FROM documents
WHERE tsvector_column @@ to_tsquery('chinese', '数据库优化')
ORDER BY rank DESC;

虽然搜索结果的排序看起来稍微合理了一些,但还是没有从根本上解决搜索结果不准确的问题。我感觉自己就像在大海里划船,虽然一直在努力,但就是到不了对岸。

三、发现“神秘宝藏”:同义词和停用词的妙用

就在我几乎要放弃的时候,我突然想到了同义词和停用词的作用。在中文里,很多词有相同或相近的意思,如果能把这些同义词也考虑进去,搜索结果肯定会更准确。而且,一些常用的停用词,如“的”“是”“在”等,对搜索结果没有实际意义,应该把它们过滤掉。

1. 同义词词典的创建与应用

我创建了一个同义词词典,把和“数据库优化”相关的同义词都列出来。首先,创建一个同义词文件 synonyms.txt,内容如下:

数据库优化 数据库性能优化 数据库调优

然后,创建同义词词典:

CREATE TEXT SEARCH DICTIONARY chinese_synonym (
    TEMPLATE = synonym,
    SYNONYMS = synonyms
);

接着,修改中文文本搜索配置,使用这个同义词词典:

ALTER TEXT SEARCH CONFIGURATION chinese
ALTER MAPPING FOR asciiword, word, numword, hword_asciiword, hword, hword_numword
WITH chinese_synonym, simple;

最后,更新 tsvector_column 列,使用新的配置:

UPDATE documents
SET tsvector_column = to_tsvector('chinese', title ||'' || content);

我再次运行搜索查询:

SELECT title
FROM documents
WHERE tsvector_column @@ to_tsquery('chinese', '数据库优化');

这次搜索结果有了明显的改善,一些包含“数据库性能优化”和“数据库调优”的文档也被正确地搜索出来了,就像在黑暗中突然看到了一丝曙光。

2. 停用词的过滤处理

接下来,我处理停用词。我创建了一个停用词文件 stopwords.txt,把常用的停用词都列进去:

的
是
在
和
与

然后,修改中文文本搜索配置,使用这个停用词文件:

ALTER TEXT SEARCH CONFIGURATION chinese
ALTER MAPPING FOR asciiword, word, numword, hword_asciiword, hword, hword_numword
WITH simple, (simple, 'StopWords = stopwords');

再次更新 tsvector_column 列:

UPDATE documents
SET tsvector_column = to_tsvector('chinese', title ||'' || content);

我又运行搜索查询,搜索结果更加准确了,那些因为停用词干扰而出现的无关文档都被过滤掉了,就像给搜索结果做了一次大扫除,变得干干净净。

四、进一步优化:结合词组搜索和模糊搜索

虽然通过同义词和停用词的处理,搜索结果已经有了很大的改善,但我觉得还可以进一步优化。我想到了词组搜索和模糊搜索。

1. 词组搜索的实现

有时候,用户希望搜索的是一个完整的词组,而不是单个词的组合。我使用 phraseto_tsquery 函数来实现词组搜索。例如,用户搜索“数据库优化”,查询语句如下:

SELECT title
FROM documents
WHERE tsvector_column @@ phraseto_tsquery('chinese', '数据库优化');

这样,只有包含完整“数据库优化”词组的文档才会被搜索出来,搜索结果更加精确,就像用一把精准的手术刀,把需要的信息准确地切割出来。

2. 模糊搜索的加入

为了提高搜索的灵活性,我还加入了模糊搜索。我使用 websearch_to_tsquery 函数,它支持模糊匹配和更自然的搜索语法。例如,用户搜索“数据库优化 相关”,查询语句如下:

SELECT title
FROM documents
WHERE tsvector_column @@ websearch_to_tsquery('chinese', '数据库优化 相关');

这样,即使文档中没有完全匹配“数据库优化”的内容,但包含了相关的词,也可能被搜索出来,大大提高了搜索的覆盖率,就像撒了一张更大的网,能捞到更多的“鱼”。

五、性能优化:索引和定期更新

在解决了搜索结果不准确的问题后,我还考虑了搜索性能的优化。毕竟,如果搜索速度很慢,即使结果再准确,用户体验也会很差。

1. 全文搜索索引的创建

我给 tsvector_column 列创建了一个全文搜索索引:

CREATE INDEX idx_documents_tsvector ON documents USING gin(tsvector_column);

GIN(Generalized Inverted Index)索引非常适合全文搜索,它可以快速定位到包含搜索词的文档。创建索引后,搜索速度有了显著提升,就像给汽车换了一个更强大的发动机,跑得更快了。

2. 定期更新向量列

随着文档的不断增加和修改,tsvector_column 列中的内容也需要定期更新,以保证搜索结果的准确性。我写了一个定时任务,每天凌晨更新 tsvector_column 列:

UPDATE documents
SET tsvector_column = to_tsvector('chinese', title ||'' || content);

这样,即使有新的文档加入或者旧的文档被修改,搜索结果也能及时反映最新的情况。

六、收获与展望

经过这次和 PostgreSQL 全文搜索“小怪兽”的激烈战斗,我收获颇丰。我深刻认识到,在使用全文搜索功能时,分词规则、搜索查询表达式、权重排名、同义词、停用词、词组搜索、模糊搜索以及性能优化等方面都需要综合考虑,任何一个环节出问题都可能导致搜索结果不准确或者性能低下。

现在,我们的知识管理系统的搜索功能变得又准确又快速,用户们终于不再抱怨搜索结果乱七八糟了。看着用户们满意的笑容,我心里充满了成就感,就像一个成功拯救了世界的英雄。

在未来的开发中,我还打算进一步探索 PostgreSQL 全文搜索的其他高级功能,比如基于机器学习的搜索结果排序、多语言搜索等。我相信,随着技术的不断发展,PostgreSQL 全文搜索功能会越来越强大,能为我们处理各种复杂的搜索需求提供更好的支持。同时,我也希望通过分享我的经验,能帮助更多的开发者更好地使用 PostgreSQL 的全文搜索功能,少走一些弯路,让大家在数据库的世界里更加轻松愉快地“玩耍”!说不定哪天,我又能发现一些新的“宝藏”,让 PostgreSQL 全文搜索这个“小怪兽”变得更加温顺听话呢!