PostgreSQL 全文检索深度指南:内置 FTS、zhparser 与 pg_search 全解

在 AI 与海量文本检索的时代,PostgreSQL 凭借原生全文检索能力与丰富扩展,成为轻量、高效、可直接落地的文本搜索与数据存储一体化方案。

一. 什么是全文检索

全文检索(Full-Text Search,FTS)是一种从海量文本中快速找出相关内容的搜索技术。与常见的 LIKE '%keyword%' 不同,全文检索的核心价值在于三点:倒排索引加速,相关度排序,词根匹配

对于一个文章系统、搜索框、日志分析平台来说,全文检索几乎是标配能力。PostgreSQL 从很早就内置了完整的全文检索功能,近年来还涌现出 zhparser(中文分词)和 pg_search(BM25 相关度排序)两个强力补充,使得 PG 的检索能力足以与 Elasticsearch 一较高下。


二. PostgreSQL 内置全文检索

2.1 核心概念:tsvector 与 tsquery

PG 全文检索围绕两个数据类型工作:

tsvector --- 文档的存储格式

文档文本经过分词、去停用词、词形归一化后,生成一个"词位序列"(lexeme),每个词位带上它在原文中的位置信息。

复制代码
-- 原始字符串直接转 tsvector(空格分词)
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;
-- 输出:
-- 'a' 'and' 'ate' 'cat' 'fat' 'mat' 'on' 'rat' 'sat'

to_tsvector() --- 带语言处理的转换函数

复制代码
SELECT to_tsvector('english', 'The Fat Rats');
-- 输出:'fat':2 'rat':3

to_tsvector() 做了三件事:

  • 小写化The Fat Ratsthe fat rats

  • 词形归一化Ratsrat(复数归一)

  • 去停用词 :如 aonit 等高频无意义词被丢弃

    -- 对比:有 to_tsvector 和没有的差别
    SELECT 'a fat cat sat on a mat - it ate a fat rats'::tsvector;
    -- 输出:'-' 'a' 'ate' 'cat' 'fat' 'it' 'mat' 'on' 'rats' 'sat'

    SELECT to_tsvector('english', 'a fat cat sat on a mat - it ate a fat rats');
    -- 输出:'ate':9 'cat':3 'fat':2,11 'mat':7 'rat':12 'sat':4

注意标点符号 - 和停用词 aonitto_tsvector() 中都被过滤掉了。

tsquery --- 查询条件格式

tsquery 是经过规范化的查询条件,支持布尔逻辑:

复制代码
-- AND:同时包含
SELECT to_tsquery('english', 'cat & rat');

-- OR:包含其一
SELECT to_tsquery('english', 'cat | rat');

-- NOT:排除
SELECT to_tsquery('english', 'cat & ! rat');

-- 短语查询:词1紧跟词2
SELECT to_tsquery('english', '''fat <-> rat''');

-- 前缀匹配
SELECT to_tsquery('english', 'rat:*');

2.2 匹配操作符 @@

tsvector 和 tsquery 之间用 @@ 连接,表示"文档是否匹配查询":

复制代码
-- 两种写法等价
to_tsvector('fat cats ate rats') @@ to_tsquery('cat & rat')   -- → t
'fat cats ate rats'           @@ to_tsquery('cat & rat')       -- → t

text 类型会在比较时隐式调用 to_tsvector() 转换。

2.3 内置全文检索的局限

PG 内置分词器默认按空格分割,这对英文友好,对中文却束手无策:

复制代码
-- 中文整句被当成一个词,无法部分匹配
SELECT to_tsvector('english', '不可用或超时导致');
-- '不可用或超时导致':1

-- 精确匹配整个词组才有效
SELECT * FROM wenzhang WHERE content_tsvector @@ '不可用或超时'::tsquery;
-- → 0 rows

SELECT * FROM wenzhang WHERE content_tsvector @@ '不可用或超时导致'::tsquery;
-- → 有结果

要支持中文,需要引入中文分词插件。


三. 中文分词支持:zhparser

3.1 为什么需要 zhparser

中文文本的词与词之间没有空格分隔,直接用空格分词器会把一整句话当成一个词。zhparser 基于 SCWS 中文分词库实现,能将中文句子正确切分为独立的词语。

3.2 创建扩展和文本搜索配置

以下操作假设 zhparser 扩展已安装(编译安装步骤请参考 GitHub amutu/zhparser)。

复制代码
-- 1. 创建 zhparser 扩展
CREATE EXTENSION zhparser;

-- 2. 验证扩展
\dx zhparser
--  List of installed extensions
--  Name    | Version | Schema | Description
--  --------+---------+--------+------------------------------------------
--   zhparser | 2.3     | public | a parser for full-text search of Chinese

-- 3. 查看当前默认文本搜索配置
SHOW default_text_search_config;
-- → pg_catalog.english

-- 4. 创建中文文本搜索配置(关键步骤)
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);

-- 5. 核心步骤:将各词性映射到 simple 词典
-- 如果跳过这步,to_tsvector('chinese', ...) 返回空!
ALTER TEXT SEARCH CONFIGURATION chinese
    ADD MAPPING FOR n, v, a, i, e, l WITH simple;

mapping 中各词性的含义:

标记 词性 标记 词性
n 名词 i 成语
v 动词 e 叹词
a 形容词 l 习用语

simple 词典不做任何词形处理,原样存储和匹配,非常适合不需要词形变化的汉语。如果不指定 mapping,或者 mapping 到其他语言的词典(如 english),分词结果将是

3.3 对比分词效果

-- 默认 english 分词器:中文被当成一个词 ;-- zhparser chinese 分词:正确切分为多个词语

3.4 注意事项

1. tsquery 必须精确匹配分词结果

复制代码
--  "排查" 是完整词,可以搜到
SELECT * FROM search_wenzhang WHERE content_zh_tsvector @@ '排查'::tsquery;

--  "排" 不是独立词,搜不到任何结果
SELECT * FROM search_wenzhang WHERE content_zh_tsvector @@ '排'::tsquery;
-- → 0 rows

2. 不配置 mapping 导致分词为空

复制代码
-- mapping 缺失时,to_tsvector('chinese', ...) 返回空!
-- 表现:更新后 content_zh_tsvector 字段为空

四. 智能检索:pg_search(ParadeDB)

4.2 创建扩展

以下操作假设 pg_search 扩展已安装(安装步骤请参考 ParadeDB 官方文档)。

需要预先在 postgresql.conf 中添加:

复制代码
shared_preload_libraries = 'pg_search'

重启 PG 后执行:

复制代码
CREATE EXTENSION pg_search;

目前HULK的PostgreSQL实例默认已经安装了pg_search扩展,方便大家使用。

4.3 可用的中文分词器

复制代码
SELECT * FROM paradedb.tokenizers();
 default
 keyword
 keyword_deprecated
 raw
 literal_normalized
 white_space
 regex_tokenizer
 chinese_compatible
 source_code
 ngram
 chinese_lindera
 japanese_lindera
 korean_lindera
 jieba
 lindera
 unicode_words_deprecated
 unicode_words

其中与中文相关的分词器主要是以下四个:

4.4 创建 BM25 索引

BM25 索引是 pg_search 的核心,语法与普通 B-Tree 索引不同:

复制代码
CREATE INDEX ch_idx ON public.search_wenzhang
USING bm25 (id, content)
WITH (
    key_field = 'id',
    text_fields = '{
        "content": {"tokenizer": {"type": "chinese_compatible"}}
    }'
);

参数说明:

参数 含义
key_field 主键字段,必须唯一
text_fields 要索引的文本字段及配置
tokenizer.type 使用的分词器类型

4.5 查询语法

复制代码
-- 基础查询:content 字段包含"问题"
SELECT * FROM search_wenzhang
WHERE id @@@ paradedb.parse('content:问题')
LIMIT 10;

更多查询示例:

复制代码
-- 搜索英文 "TiKV"
SELECT * FROM search_wenzhang
WHERE id @@@ paradedb.parse('content:TiKV')
LIMIT 10;

-- 搜索"定位"
SELECT * FROM search_wenzhang
WHERE id @@@ paradedb.parse('content:定位')
LIMIT 10;

-- 前缀匹配:Ti 可以匹配 TiKV、TiDB 等
SELECT * FROM search_wenzhang
WHERE id @@@ paradedb.parse('content:Ti*')
LIMIT 10;

-- 前缀匹配查不到的情况(英文分词器按空格分词)
SELECT * FROM search_wenzhang
WHERE id @@@ paradedb.parse('content:Ti')
LIMIT 10;
-- → 0 rows(需要完整词 "TiKV" 或前缀 "Ti*")

注意:id @@@ ... 是 pg_search 的查询语法,BM25 索引不依赖额外的 tsvector 存储列。


4.6中文分词zhparser和智能搜索pg_search查询对比

--查询关键词**"问题"**

复制代码
--pg_search
dbrte=# SELECT id, content  FROM search_wenzhang  WHERE id @@@ paradedb.parse('content:问题')  LIMIT 2;
 id |                                    content                                    
----+-------------------------------------------------------------------------------
  1 | 通过 HTTP API 来排查 goroutine 泄露的问题。 
  2 | 该问题通常由于 TiKV Region 不可用或超时导致,需要看 TiKV 的监控指标定位问题。

--zhparser
dbrte=# SELECT id, content  FROM search_wenzhang  WHERE content_zh_tsvector @@ '问题'::tsquery  LIMIT 2 ;
 id |                                    content                                    
----+-------------------------------------------------------------------------------
  1 | 通过 HTTP API 来排查 goroutine 泄露的问题。 
  2 | 该问题通常由于 TiKV Region 不可用或超时导致,需要看 TiKV 的监控指标定位问题。

--查询关键字**"问"**

复制代码
--pg_serach
dbrte=# SELECT id, content  FROM search_wenzhang  WHERE id @@@ paradedb.parse('content:问')  LIMIT 2;
 id |                                    content                                    
----+-------------------------------------------------------------------------------
  1 | 通过 HTTP API 来排查 goroutine 泄露的问题。 
  2 | 该问题通常由于 TiKV Region 不可用或超时导致,需要看 TiKV 的监控指标定位问题。

--zhparser
dbrte=# SELECT id, content  FROM search_wenzhang  WHERE content_zh_tsvector @@ '问'::tsquery  LIMIT 2;
 id | content 
----+---------
(0 rows)

结论:可以看出pg_search在非常规词组的匹配上更准确。


五. 选型建议与最佳实践

5.1三种方案横向对比

5.2 选型决策

我们可以根据以下策略进行分词器选择:

5.3 表设计建议:触发器自动维护 tsvector

每次 INSERT/UPDATE 都手动调用 to_tsvector() 容易遗漏。用触发器自动完成:

复制代码
CREATE OR REPLACE FUNCTION update_wenzhang_tsvector()
RETURNS TRIGGER AS $$
BEGIN
    NEW.content_tsvector := to_tsvector(
        'english',
        COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.content, '')
    );
    NEW.content_zh_tsvector := to_tsvector(
        'chinese',
        COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.content, '')
    );
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER wenzhang_tsvector_update
BEFORE INSERT OR UPDATE ON search_wenzhang
FOR EACH ROW EXECUTE FUNCTION update_wenzhang_tsvector();

有了这个触发器,应用程序只需要写入 content 字段,tsvector 列会自动保持最新。

5.4 性能优化清单

  • 建索引:GIN 索引(内置/zhparser)或 BM25 索引(pg_search)是性能的根基

  • 预先生成 tsvector :不要在查询时实时调用 to_tsvector(),那是 CPU 密集操作

  • 分区表:按时间对大表做 Range 分区(如按月),减少每次检索扫描的数据量

  • 定期维护VACUUM ANALYZE 保持统计信息准确;REINDEX 保持索引效率

  • 避免 LIKE :所有文本搜索场景一律使用全文检索,删除所有 LIKE '%keyword%'

以下是基于200 万行数据进行的查询性能基准测试:

查询性能对比:

GIN 索引让查询从全表扫描变为索引查找,性能提升约 7 倍

5.5 常见问题速查

现象 最可能的原因 解决方案
to_tsvector('chinese', ...) 返回空 没有配置 mapping 执行 ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR ... WITH simple
中文查询只能搜完整词,部分匹配不到 tsquery 精确匹配分词结果 使用 chinese_compatiblejieba 分词器,支持更细粒度切分
pg_search 查询报错 shared_preload_libraries 未配置 修改 postgresql.conf,重启 PG
查询变慢 索引失效或数据量增长 执行 REINDEX,检查分区策略
分词结果不符合预期 词典配置问题 检查 SCWS 词典版本,或换用 pg_search 的 jieba 分词器

六. 总结

PostgreSQL 的全文检索生态已经从"能用"进化到了"好用":

  1. PG 内置 FTS:英文场景完全够用,内置无需安装,性能稳定

  2. zhparser:解决中文布尔搜索问题,代价是需要编译安装插件,且只有"有/没有"的判断

  3. pg_search:中文 + BM25 排序的最佳方案,安装简单,功能强大,是当前最推荐的生产级选择

在实际项目中,可以将多种方案组合使用:用 zhparser 或 pg_search 做中文全文检索,同时保留内置 FTS 处理英文场景,各取所长。

相关推荐
水彩橘子1 天前
PostgreSQL Streaming Replication 主从
数据库·postgresql
亚马逊云开发者1 天前
Amazon Aurora PostgreSQL 快速配置实战:两次点击秒级创建无服务器数据库,告别 VPC 子网安全组配置噩梦
数据库·postgresql·serverless
源码之屋1 天前
计算机毕业设计:Python天气数据采集与可视化分析平台 Django框架 线性回归 数据分析 大数据 机器学习 大模型 气象数据(建议收藏)✅
人工智能·python·深度学习·算法·django·线性回归·课程设计
羑悻的小杀马特1 天前
PostgreSQL主从复制实战,告别单点故障,附主从切换与延迟监控命令。
运维·服务器·数据库·人工智能·postgresql
架构师老Y1 天前
003、Python Web框架深度对比:Django vs Flask vs FastAPI
前端·python·django
阿里小阿希2 天前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
WangJunXiang62 天前
第09章:PostgreSQL日常维护
数据库·postgresql
dyyshb2 天前
PostgreSQL 终极兜底方案
数据库·postgresql
Mr.徐大人ゞ2 天前
2-5.全文索引与并行查询
postgresql
暴力袋鼠哥2 天前
基于 Django 与 Vue 的汽车数据分析系统设计与实现
vue.js·django·汽车