PostgreSQL: PostgreSQL全文搜索引擎

最后更新:2026-08-26

1. 你将学到


2. 故事

Charlie 的电商平台有 500,000 条商品数据,用户需要搜索商品。最初他用 LIKE '%red shoes%' 实现,但搜索 "red shoes" 只返回描述中恰好包含 "red shoes" 这个连续字符串的商品,而且查询需要 3 秒。Charlie 改用 PostgreSQL 全文搜索后:


3. Concept:全文搜索核心概念

(1) 四个核心概念

概念 说明 PostgreSQL 对应
文档 (Document) 待搜索的文本内容 tsvector
查询 (Query) 用户的搜索词 tsquery
字典 (Dictionary) 定义如何处理词(停用词、词干化) pg_ts_dict 系统表
配置 (Configuration) 组合解析器和字典 pg_ts_config 系统表

(2) 全文搜索处理管线

100%
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]

▶ 示例:查看文本被如何处理

SQL
SELECT * FROM ts_debug('english', 'The red shoes are beautiful');
TEXT 📖 仅展示
 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

SQL
SELECT to_tsvector('english', 'The red shoes are beautiful shoes');
TEXT 📖 仅展示
'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

SQL
SELECT to_tsquery('english', 'red & shoe');
-- 'red' & 'shoe'

SELECT to_tsquery('english', 'red | blue');
-- 'red' | 'blue'

输出:

TEXT 📖 仅展示
 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

SQL
SELECT plainto_tsquery('english', 'red shoes');
-- 'red' & 'shoe'  (matches docs with BOTH terms)

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ 示例:phraseto_tsquery——短语搜索

SQL
SELECT phraseto_tsquery('english', 'red shoes');
-- 'red' <-> 'shoe'  (matches docs where 'red' is immediately before 'shoe')

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ 示例:websearch_to_tsquery——搜索引擎语法

SQL
SELECT websearch_to_tsquery('english', '"red shoes" -broken OR new');
-- 'red' <-> 'shoe' & !'broken' | 'new'

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
websearch 语法 含义
"exact phrase" 短语匹配(<->)
-word 排除(!)
word1 OR word2 或(|)
word1 word2 与(&)

▶ 示例:使用 @@ 操作符搜索

SQL
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');

输出:

TEXT 📖 仅展示
INSERT 0 1

6. Concept:排名与高亮

(1) ts_rank 排名

ts_rank 计算查询与文档的匹配度,返回 0~1 的浮点数,值越大越相关。

▶ 示例:按相关度排序

SQL
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;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
排名函数 说明 适用场景
ts_rank(vector, query) 基于词频排名 通用排名
ts_rank_cd(vector, query) 覆盖密度排名 更精确的短文档排名
ts_rank(vector, query, weights) 带权重排名 标题权重 > 正文

▶ 示例:带权重排名(标题权重高于描述)

SQL
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;

输出:

TEXT 📖 仅展示
UPDATE 3
权重字母 默认权重值 典型用途
A 1.0 标题
B 0.4 正文
C 0.2 摘要
D 0.1 附加信息

(2) ts_headline 高亮

ts_headline 在原文中标记匹配词项。

▶ 示例:搜索结果高亮

SQL
SELECT name,
       ts_headline('english', description, plainto_tsquery('english', 'red shoes')) AS highlighted
FROM catalog
WHERE search_vector @@ plainto_tsquery('english', 'red shoes');
TEXT 📖 仅展示
 name              | highlighted
-------------------+-------------------------------------------------
 Red Running Shoes | Lightweight <b>red</b> <b>shoes</b> for running

▶ 示例:自定义高亮标签

SQL
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');

输出:

TEXT 📖 仅展示
 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 索引

SQL
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');

输出:

TEXT 📖 仅展示
CREATE TABLE

▶ 示例:基于存储 tsvector 列的 GIN 索引

SQL
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');

输出:

TEXT 📖 仅展示
CREATE TABLE

▶ 示例:自动同步 tsvector 列的触发器

SQL
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();

输出:

TEXT 📖 仅展示
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 配置不过滤不停用词

SQL
SELECT to_tsvector('simple', 'The red shoes');
-- 'red':2 'shoes':3 'the':1  (keeps 'the', no stemming)

输出:

TEXT 📖 仅展示
 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

▶ 示例:应用层分词方案(无需扩展)

SQL
-- 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', '红色 & 跑鞋');

输出:

TEXT 📖 仅展示
INSERT 0 1

▶ 示例:混合中英文搜索

SQL
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', '数据 & 优化');

输出:

TEXT 📖 仅展示
UPDATE 3

9. Concept:统计与系统表

(1) ts_stat 统计词频

ts_stat 帮助分析 tsvector 中的词项分布,常用于优化搜索配置。

▶ 示例:查看最常见词项

SQL
SELECT word, ndoc, nentry
FROM ts_stat(
  'SELECT search_vector FROM catalog'
)
ORDER BY nentry DESC
LIMIT 10;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)
说明
word 词项(lexeme)
ndoc 出现在多少文档中
nentry 总出现次数

(2) 系统表

▶ 示例:查看可用配置

SQL
SELECT cfgname FROM pg_ts_config;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ 示例:查看可用字典

SQL
SELECT dictname, dictnamespace FROM pg_ts_dict;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ 示例:LIKE vs 全文搜索性能对比

SQL
-- 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');

输出:

TEXT 📖 仅展示
INSERT 0 1
查询方式 耗时 索引支持 功能
LIKE '%keyword%' ~3000ms 精确子串匹配
LIKE 'keyword%' ~5ms B-tree 前缀匹配
@@ 全文搜索 + GIN ~30ms GIN 词项匹配+词干化+排名

10. 实战:电商商品搜索引擎

Charlie 需要为电商平台实现完整的商品搜索功能,包括多字段搜索、排名、高亮和分页。

SQL
-- 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;

❓ 常见问题

Q 全文搜索和 LIKE 有什么本质区别?
A LIKE 是精确的子串匹配,不理解语义;全文搜索会分词、去停用词、词干化,按词项匹配,支持布尔组合和排名。全文搜索有 GIN 索引支持,LIKE 的前导通配符无法使用索引。
Q to_tsquery 和 plainto_tsquery 该用哪个?
A 如果用户直接输入搜索词,用 plainto_tsquery 或 websearch_to_tsquery(PG 11+),它们把纯文本自动转为查询。to_tsquery 需要手动写 & | ! 操作符,适合程序生成查询。
Q 为什么必须用相同的配置(config)创建索引和查询?
A 不同配置的分词和词干化结果不同。如果索引用 english 配置,查询用 simple 配置,词项不匹配,搜索结果为空。必须保持一致。
Q GENERATED ALWAYS AS ... STORED 列和触发器哪个好?
A GENERATED 列(PG 12+)自动计算,无需触发器,推荐使用。如果 PG 版本低于 12 或需要跨表组合 tsvector,则用触发器。
Q tsvector 的位置信息有什么用?
A 位置信息用于短语搜索(phraseto_tsquery 使用 <-> 操作符匹配相邻词项)和邻近搜索(`<N>` 距离操作符)。如果不需要短语搜索,可以在 to_tsvector 时忽略位置。
Q GIN 索引会影响写入性能吗?
A 会。GIN 索引的更新比 B-tree 慢,因为需要合并倒排列表。PostgreSQL 默认开启 FASTUPDATE,将更新先缓存再批量合并,牺牲少量查询实时性换取写入性能。
Q 中文全文搜索该怎么做?
A PostgreSQL 原生不支持中文分词。推荐方案:安装 zhparser/pg_jieba 扩展(原生分词),或在应用层用分词库预先分词后用 simple 配置存入 tsvector。应用层方案最简单,无需安装扩展。

📖 小节


📝 作业

  1. ⭐ 创建一张 blog_posts 表,包含 title(TEXT)和 body(TEXT),插入 3 条数据,使用 to_tsvector + plainto_tsquery 搜索包含 "database" 的文章。

  2. ⭐⭐ 为 blog_posts 表添加 search_vector 列(tsvector),创建 GIN 索引,编写触发器在 INSERT/UPDATE 时自动更新 search_vector(title 权重 A,body 权重 B),并使用 ts_rank + ts_headline 搜索 "performance optimization" 并按排名排序。

  3. ⭐⭐⭐ 设计一个支持中英文混合搜索的 knowledge_articles 表:英文内容用 english 配置,中文内容用 simple 配置(应用层预分词),创建两个 tsvector 列和对应 GIN 索引。编写一个搜索函数,根据输入查询语言自动选择对应列搜索,结果用 ts_headline 高亮并按 ts_rank 排序,支持分页。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏