PostgreSQL: PostgreSQL全文搜索引擎
最后更新:2026-08-26
1. 你将学到
- 理解全文搜索的核心概念(文档、词项、字典、配置)
- 使用
tsvector和tsquery数据类型 - 使用
to_tsvector/to_tsquery/plainto_tsquery/phraseto_tsquery/websearch_to_tsquery转换 - 使用
@@操作符执行全文搜索 - 使用
ts_rank排名和ts_headline高亮 - 创建 GIN 索引加速全文搜索
- 配置多语言全文搜索(english/simple/zhpinyin)
- 使用
ts_stat统计和系统表查询
2. 故事
Charlie 的电商平台有 500,000 条商品数据,用户需要搜索商品。最初他用 LIKE '%red shoes%' 实现,但搜索 "red shoes" 只返回描述中恰好包含 "red shoes" 这个连续字符串的商品,而且查询需要 3 秒。Charlie 改用 PostgreSQL 全文搜索后:
- "red shoes" 能匹配包含 "red" 和 "shoes" 的商品(不管顺序)
- 自动忽略停用词(the/a/an)
- 自动词干化(shoes → shoe)
- GIN 索引加速,查询仅需 30 毫秒,快 100 倍
3. Concept:全文搜索核心概念
(1) 四个核心概念
| 概念 | 说明 | PostgreSQL 对应 |
|---|---|---|
| 文档 (Document) | 待搜索的文本内容 | tsvector |
| 查询 (Query) | 用户的搜索词 | tsquery |
| 字典 (Dictionary) | 定义如何处理词(停用词、词干化) | pg_ts_dict 系统表 |
| 配置 (Configuration) | 组合解析器和字典 | pg_ts_config 系统表 |
(2) 全文搜索处理管线
flowchart LR
A[Raw Text] --> B[Parser<br/>Tokenize]
B --> C[Dictionary<br/>Stemming + Stop words]
C --> D[tsvector<br/>Sorted lexemes]
D --> E[GIN Index<br/>Fast lookup]
F[User Query] --> G[to_tsquery<br/>Parse & normalize]
G --> H[tsquery<br/>Lexemes + operators]
E --> I["@@ operator<br/>Match"]
H --> I
I --> J[ts_rank<br/>Scoring]
J --> K[ts_headline<br/>Highlighting]
K --> L[Results]
▶ 示例:查看文本被如何处理
SELECT * FROM ts_debug('english', 'The red shoes are beautiful');
alias | descriptor | token | dictionaries | dictionary | lexemes
-------+------------+--------+----------------+------------+---------
ascii | word | The | {english_stem} | english_stem| {}
ascii | word | red | {english_stem} | english_stem| {red}
ascii | word | shoes | {english_stem} | english_stem| {shoe}
ascii | word | are | {english_stem} | english_stem| {}
ascii | word | beautiful | {english_stem}| english_stem| {beauti}
4. Concept:tsvector 与 tsquery
(1) tsvector——文档的预处理结果
tsvector 是排序去重后的词项(lexeme)列表,每个词项带有位置信息。
▶ 示例:将文本转为 tsvector
SELECT to_tsvector('english', 'The red shoes are beautiful shoes');
'beauti':5 'red':2 'shoe':3,6
| 处理步骤 | 输入 | 输出 |
|---|---|---|
| 分词 | The red shoes are beautiful shoes | [The, red, shoes, are, beautiful, shoes] |
| 去停用词 | [The, red, shoes, are, beautiful, shoes] | [red, shoes, beautiful, shoes] |
| 词干化 | [red, shoes, beautiful, shoes] | [red, shoe, beauti, shoe] |
| 去重+位置 | [red, shoe, beauti, shoe] | 'beauti':5 'red':2 'shoe':3,6 |
(2) tsquery——查询的预处理结果
tsquery 是词项与布尔操作符的组合。
| 操作符 | 含义 | 示例 |
|---|---|---|
& |
AND(与) | red & shoe |
| |
OR(或) | red | blue |
! |
NOT(非) | !broken |
<-> |
FOLLOWED BY(相邻) | red <-> shoe |
`<N>` |
距离 N | red <2> shoe |
▶ 示例:构建 tsquery
SELECT to_tsquery('english', 'red & shoe');
-- 'red' & 'shoe'
SELECT to_tsquery('english', 'red | blue');
-- 'red' | 'blue'
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
5. Concept:全文搜索查询函数
(1) 五种查询构建函数
| 函数 | PG 版本 | 输入格式 | 说明 |
|---|---|---|---|
to_tsquery(config, text) |
所有 | red & shoe |
需手动写操作符 |
plainto_tsquery(config, text) |
所有 | red shoes |
纯文本,自动用 & 连接 |
phraseto_tsquery(config, text) |
9.6+ | red shoes |
纯文本,自动用 <-> 连接 |
websearch_to_tsquery(config, text) |
11+ | "red shoes" -broken |
Web 搜索引擎语法 |
tsvector @@ tsquery |
所有 | — | 匹配操作符 |
▶ 示例:plainto_tsquery——纯文本自动 AND
SELECT plainto_tsquery('english', 'red shoes');
-- 'red' & 'shoe' (matches docs with BOTH terms)
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:phraseto_tsquery——短语搜索
SELECT phraseto_tsquery('english', 'red shoes');
-- 'red' <-> 'shoe' (matches docs where 'red' is immediately before 'shoe')
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:websearch_to_tsquery——搜索引擎语法
SELECT websearch_to_tsquery('english', '"red shoes" -broken OR new');
-- 'red' <-> 'shoe' & !'broken' | 'new'
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| websearch 语法 | 含义 |
|---|---|
"exact phrase" |
短语匹配(<->) |
-word |
排除(!) |
word1 OR word2 |
或(|) |
word1 word2 |
与(&) |
▶ 示例:使用 @@ 操作符搜索
CREATE TABLE catalog (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
search_vector tsvector
);
INSERT INTO catalog (name, description, search_vector) VALUES
('Red Running Shoes', 'Lightweight red shoes for running',
to_tsvector('english', 'Lightweight red shoes for running')),
('Blue Casual Shoes', 'Comfortable blue shoes for daily wear',
to_tsvector('english', 'Comfortable blue shoes for daily wear')),
('Red Dress', 'Elegant red dress for special occasions',
to_tsvector('english', 'Elegant red dress for special occasions'));
-- Search for "red shoes"
SELECT name, description
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
输出:
INSERT 0 1
6. Concept:排名与高亮
(1) ts_rank 排名
ts_rank 计算查询与文档的匹配度,返回 0~1 的浮点数,值越大越相关。
▶ 示例:按相关度排序
SELECT name,
ts_rank(search_vector, plainto_tsquery('english', 'red shoes')) AS rank
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes')
ORDER BY rank DESC;
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| 排名函数 | 说明 | 适用场景 |
|---|---|---|
ts_rank(vector, query) |
基于词频排名 | 通用排名 |
ts_rank_cd(vector, query) |
覆盖密度排名 | 更精确的短文档排名 |
ts_rank(vector, query, weights) |
带权重排名 | 标题权重 > 正文 |
▶ 示例:带权重排名(标题权重高于描述)
ALTER TABLE catalog ADD COLUMN title_vector tsvector;
UPDATE catalog SET title_vector = to_tsvector('english', name);
-- Set weights: title=D(default), body=A(highest)
SELECT name,
ts_rank(
setweight(title_vector, 'A') || setweight(search_vector, 'B'),
plainto_tsquery('english', 'red shoes')
) AS rank
FROM catalog
WHERE setweight(title_vector, 'A') || setweight(search_vector, 'B')
@@ plainto_tsquery('english', 'red shoes')
ORDER BY rank DESC;
输出:
UPDATE 3
| 权重字母 | 默认权重值 | 典型用途 |
|---|---|---|
| A | 1.0 | 标题 |
| B | 0.4 | 正文 |
| C | 0.2 | 摘要 |
| D | 0.1 | 附加信息 |
(2) ts_headline 高亮
ts_headline 在原文中标记匹配词项。
▶ 示例:搜索结果高亮
SELECT name,
ts_headline('english', description, plainto_tsquery('english', 'red shoes')) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
name | highlighted
-------------------+-------------------------------------------------
Red Running Shoes | Lightweight <b>red</b> <b>shoes</b> for running
▶ 示例:自定义高亮标签
SELECT ts_headline(
'english',
description,
plainto_tsquery('english', 'red shoes'),
'StartSel=<em>,StopSel=</em>,MaxWords=10,MinWords=5'
) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| 高亮参数 | 说明 | 默认值 |
|---|---|---|
StartSel |
匹配开始标记 | <b> |
StopSel |
匹配结束标记 | </b> |
MaxWords |
高亮片段最大词数 | 35 |
MinWords |
高亮片段最小词数 | 15 |
ShortWord |
忽略的最短词长度 | 3 |
7. Concept:GIN 索引加速
(1) 三种 GIN 索引方式
| 方式 | 语法 | 更新开销 | 适用场景 |
|---|---|---|---|
| 基于列 | gin(to_tsvector(config, column)) |
更新时需重建 | 列值不变时最佳 |
| 基于存储列 | gin(search_vector) |
更新开销低 | 高频更新,需触发器同步 |
| FASTUPDATE | 默认开启 | 延迟合并 | 写多读多场景 |
▶ 示例:基于函数的 GIN 索引
CREATE INDEX idx_catalog_desc_fts
ON catalog USING gin (to_tsvector('english', description));
-- Query must use same config
SELECT name FROM catalog
WHERE to_tsvector('english', description) @@ plainto_tsquery('english', 'red shoes');
输出:
CREATE TABLE
▶ 示例:基于存储 tsvector 列的 GIN 索引
CREATE INDEX idx_catalog_search_fts ON catalog USING gin (search_vector);
-- Query uses stored column
SELECT name FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
输出:
CREATE TABLE
▶ 示例:自动同步 tsvector 列的触发器
CREATE FUNCTION catalog_search_vector_update() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', COALESCE(NEW.name, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(NEW.description, '')), 'B');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_catalog_search
BEFORE INSERT OR UPDATE OF name, description ON catalog
FOR EACH ROW EXECUTE FUNCTION catalog_search_vector_update();
输出:
INSERT 0 1
| 方式 | 查询写法 | 索引利用 | 维护成本 |
|---|---|---|---|
| 函数索引 | to_tsvector('english', col) @@ query |
能用 | 零(自动) |
| 存储列+索引 | stored_col @@ query |
能用 | 需触发器同步 |
| 无索引 | to_tsvector('english', col) @@ query |
全表扫描 | 零 |
8. Concept:多语言配置
(1) PostgreSQL 内置配置
| 配置名 | 语言 | 说明 |
|---|---|---|
english |
英语 | 默认,支持词干化和停用词 |
simple |
无语言处理 | 仅分词,不停用词不词干化 |
zhpinyin |
中文拼音 | PG 扩展,中文转拼音搜索 |
▶ 示例:simple 配置不过滤不停用词
SELECT to_tsvector('simple', 'The red shoes');
-- 'red':2 'shoes':3 'the':1 (keeps 'the', no stemming)
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:不同配置的搜索行为对比
| 配置 | to_tsvector('the red shoes are nice') | 特点 |
|---|---|---|
| english | 'nice':5 'red':2 'shoe':3 | 去停用词+词干化 |
| simple | 'are':4 'nice':5 'red':2 'shoes':3 'the':1 | 仅小写+分词 |
| zhpinyin | 取决于扩展实现 | 中文转拼音 |
(2) 中文全文搜索方案
PostgreSQL 原生不内置中文分词。常见方案:
| 方案 | 扩展 | 分词方式 | 安装复杂度 |
|---|---|---|---|
| zhparser | PostgreSQL 扩展 | 基于 SimpleChineseSeg | 中 |
| pg_jieba | PostgreSQL 扩展 | 基于 jieba 分词 | 中 |
| zhpinyin | PostgreSQL 扩展 | 拼音搜索 | 低 |
| 应用层分词 | 无 | 应用生成 tsvector | 低 |
▶ 示例:应用层分词方案(无需扩展)
-- Pre-segment Chinese text with spaces at application layer
INSERT INTO catalog (name, description, search_vector)
VALUES (
'红色跑鞋',
'轻 量 红色 跑鞋 适合 跑步',
to_tsvector('simple', '轻 量 红色 跑鞋 适合 跑步')
);
-- Search with segmented query
SELECT name FROM catalog
WHERE search_vector @@ to_tsquery('simple', '红色 & 跑鞋');
输出:
INSERT 0 1
▶ 示例:混合中英文搜索
CREATE TABLE articles (
article_id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT,
fts_en tsvector,
fts_zh tsvector
);
-- English config for English content, simple for Chinese
UPDATE articles SET
fts_en = to_tsvector('english', COALESCE(title, '') || ' ' || COALESCE(content, '')),
fts_zh = to_tsvector('simple', COALESCE(title, '') || ' ' || COALESCE(content, ''));
-- Search English
SELECT title FROM articles
WHERE fts_en @@ plainto_tsquery('english', 'database optimization');
-- Search Chinese (application-segmented)
SELECT title FROM articles
WHERE fts_zh @@ to_tsquery('simple', '数据 & 优化');
输出:
UPDATE 3
9. Concept:统计与系统表
(1) ts_stat 统计词频
ts_stat 帮助分析 tsvector 中的词项分布,常用于优化搜索配置。
▶ 示例:查看最常见词项
SELECT word, ndoc, nentry
FROM ts_stat(
'SELECT search_vector FROM catalog'
)
ORDER BY nentry DESC
LIMIT 10;
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
| 列 | 说明 |
|---|---|
word |
词项(lexeme) |
ndoc |
出现在多少文档中 |
nentry |
总出现次数 |
(2) 系统表
▶ 示例:查看可用配置
SELECT cfgname FROM pg_ts_config;
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:查看可用字典
SELECT dictname, dictnamespace FROM pg_ts_dict;
输出:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ 示例:LIKE vs 全文搜索性能对比
-- Create test data
INSERT INTO catalog (name, description, search_vector)
SELECT
'Product ' || i,
'High quality product number ' || i || ' with great features and amazing design',
to_tsvector('english', 'High quality product number ' || i || ' with great features and amazing design')
FROM generate_series(1, 100000) AS i;
-- LIKE query (slow, no index support for leading wildcard)
EXPLAIN ANALYZE
SELECT name FROM catalog WHERE description LIKE '%great features%';
-- Full-text search with GIN index (fast)
CREATE INDEX idx_catalog_fts ON catalog USING gin (search_vector);
EXPLAIN ANALYZE
SELECT name FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'great features');
输出:
INSERT 0 1
| 查询方式 | 耗时 | 索引支持 | 功能 |
|---|---|---|---|
LIKE '%keyword%' |
~3000ms | 无 | 精确子串匹配 |
LIKE 'keyword%' |
~5ms | B-tree | 前缀匹配 |
@@ 全文搜索 + GIN |
~30ms | GIN | 词项匹配+词干化+排名 |
10. 实战:电商商品搜索引擎
Charlie 需要为电商平台实现完整的商品搜索功能,包括多字段搜索、排名、高亮和分页。
-- Step 1: Create products table with pre-computed tsvector
CREATE TABLE shop_products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT,
price NUMERIC(10,2) NOT NULL,
search_doc tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('english', COALESCE(name, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(category, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(description, '')), 'C')
) STORED
);
-- Step 2: Insert sample data
INSERT INTO shop_products (name, category, description, price) VALUES
('Red Running Shoes', 'Shoes', 'Lightweight red running shoes with cushioned sole', 89.99),
('Blue Casual Shoes', 'Shoes', 'Comfortable blue shoes for everyday casual wear', 59.99),
('Red Leather Dress Shoes', 'Shoes', 'Premium red leather dress shoes for formal events', 149.99),
('Red Silk Dress', 'Dresses', 'Elegant red silk dress for special occasions', 199.99),
('Running Watch', 'Accessories', 'GPS running watch with heart rate monitor', 249.99),
('Running Shorts', 'Clothing', 'Breathable running shorts with pockets', 39.99);
-- Step 3: Create GIN index on generated column
CREATE INDEX idx_shop_products_search ON shop_products USING gin (search_doc);
-- Step 4: Search function with ranking, highlighting and pagination
CREATE FUNCTION search_products(
p_query TEXT,
p_limit INT DEFAULT 20,
p_offset INT DEFAULT 0
) RETURNS TABLE (
product_id INT,
name TEXT,
category TEXT,
price NUMERIC,
rank REAL,
headline TEXT
) AS $$
BEGIN
RETURN QUERY
SELECT
s.product_id,
s.name,
s.category,
s.price,
ts_rank(s.search_doc, websearch_to_tsquery('english', p_query)) AS rank,
ts_headline(
'english',
COALESCE(s.name, '') || '. ' || COALESCE(s.description, ''),
websearch_to_tsquery('english', p_query),
'StartSel=<mark>,StopSel=</mark>,MaxWords=20,MinWords=5'
) AS headline
FROM shop_products s
WHERE s.search_doc @@ websearch_to_tsquery('english', p_query)
ORDER BY rank DESC
LIMIT p_limit
OFFSET p_offset;
END;
$$ LANGUAGE plpgsql;
-- Step 5: Test search
SELECT * FROM search_products('red shoes');
SELECT * FROM search_products('"running shoes"');
SELECT * FROM search_products('running -shoes');
-- Step 6: Search with category filter
SELECT name, price,
ts_rank(search_doc, plainto_tsquery('english', 'red')) AS rank
FROM shop_products
WHERE search_doc @@ plainto_tsquery('english', 'red')
AND category = 'Shoes'
ORDER BY rank DESC;
❓ 常见问题
<-> 操作符匹配相邻词项)和邻近搜索(`<N>` 距离操作符)。如果不需要短语搜索,可以在 to_tsvector 时忽略位置。📖 小节
- 全文搜索通过分词、去停用词、词干化将文本转为 tsvector,查询转为 tsquery
plainto_tsquery适合用户输入,websearch_to_tsquery(PG 11+)支持搜索引擎语法@@操作符执行匹配,ts_rank排名,ts_headline高亮- GIN 索引是全文搜索性能的关键,函数索引或存储列索引各有适用场景
- 权重(setweight A/B/C/D)让标题匹配排名高于正文
- 多语言用不同配置,中文需 zhparser/pg_jieba 扩展或应用层分词
ts_stat分析词频,系统表pg_ts_config/pg_ts_dict查看配置
📝 作业
-
⭐ 创建一张
blog_posts表,包含title(TEXT)和body(TEXT),插入 3 条数据,使用to_tsvector+plainto_tsquery搜索包含 "database" 的文章。 -
⭐⭐ 为
blog_posts表添加search_vector列(tsvector),创建 GIN 索引,编写触发器在 INSERT/UPDATE 时自动更新 search_vector(title 权重 A,body 权重 B),并使用ts_rank+ts_headline搜索 "performance optimization" 并按排名排序。 -
⭐⭐⭐ 设计一个支持中英文混合搜索的
knowledge_articles表:英文内容用 english 配置,中文内容用 simple 配置(应用层预分词),创建两个 tsvector 列和对应 GIN 索引。编写一个搜索函数,根据输入查询语言自动选择对应列搜索,结果用ts_headline高亮并按ts_rank排序,支持分页。