中文分词与 pg_jieba
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read263 words

中文分词与 pg_jieba

中文是无分隔符的语言——"数据库性能优化"到底是"数据库/性能/优化"还是"数据库性能/优化"?PostgreSQL 内置的分词器无法处理中文,pg_jieba 扩展基于结巴分词算法,让 PostgreSQL 支持中文全文检索。


为什么 LIKE 不够用

-- ❌ LIKE 的问题:
SELECT * FROM articles WHERE body LIKE '%数据库%';
-- 问题1:全表扫描,不走索引,O(N) 复杂度
-- 问题2:只能按字符串匹配,找不到"数据" + "库" 分开的场景
-- 问题3:没有相关性排序
-- ✅ 全文搜索的优势:
-- 1. GIN 索引,毫秒级响应
-- 2. 分词后能找到语义相关的文档
-- 3. ts_rank 提供相关性排序

安装 pg_jieba

# Ubuntu / Debian
sudo apt-get install postgresql-16-jieba
# 或从源码编译(需要 jieba-cppjieba 库)
# macOS (Homebrew,若有 tap)
brew install pg_jieba
# Docker:使用包含 pg_jieba 的镜像
# docker pull abcfy2/zhparser  # 或 pg_jieba 的 Docker 镜像
# 在 postgresql.conf 中加载(需重启)
# 或者只对特定数据库启用
-- 创建扩展
CREATE EXTENSION pg_jieba;
-- 验证安装
SELECT pgjieba_cut('数据库性能优化实战', true);
-- 结果:{数据库,性能,优化,实战}

配置中文全文搜索

-- 创建中文文本搜索配置
CREATE TEXT SEARCH CONFIGURATION chinese (COPY = simple);
-- 添加 pg_jieba 解析器
ALTER TEXT SEARCH CONFIGURATION chinese
ALTER MAPPING FOR n, v, a, i, e, l  -- 名词、动词、形容词等词性
WITH jieba_stem;
-- 测试分词效果
SELECT to_tsvector('chinese', '数据库性能优化实战');
-- 结果:'优化':3 '实战':4 '数据库':1 '性能':2
SELECT to_tsvector('chinese', 'PostgreSQL索引策略与查询优化');
-- 结果:'postgresql':1 '优化':5 '查询':4 '索引':2 '策略':3

创建支持中文搜索的表

CREATE TABLE zh_articles (
id           BIGSERIAL PRIMARY KEY,
title        TEXT NOT NULL,
body         TEXT,
author       TEXT,
published_at TIMESTAMPTZ DEFAULT NOW(),
search_vector TSVECTOR
GENERATED ALWAYS AS (
setweight(to_tsvector('chinese', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('chinese', COALESCE(body, '')), 'B')
) STORED
);
-- 建 GIN 索引
CREATE INDEX idx_zh_articles_search
ON zh_articles USING GIN (search_vector);
-- 插入测试数据
INSERT INTO zh_articles (title, body) VALUES
('PostgreSQL 索引策略详解',
'本文介绍 PostgreSQL 的 B-tree、GIN、GiST 等索引类型,以及如何根据查询模式选择合适的索引。'),
('数据库性能优化实战',
'通过 EXPLAIN ANALYZE 分析查询计划,识别慢查询,使用索引和查询改写提升性能。'),
('JSONB 半结构化数据存储',
'使用 JSONB 存储灵活的属性数据,配合 GIN 索引实现高效的 JSON 内部查询。');

中文全文搜索查询

-- 基础搜索
SELECT id, title
FROM zh_articles
WHERE search_vector @@ to_tsquery('chinese', '索引 & 性能');
-- 用 plainto_tsquery(更友好,自动分词并用 & 连接)
SELECT id, title
FROM zh_articles
WHERE search_vector @@ plainto_tsquery('chinese', '数据库索引优化');
-- 带相关性排序
SELECT
id,
title,
ts_rank_cd(search_vector, query) AS relevance
FROM zh_articles,
plainto_tsquery('chinese', '索引优化') AS query
WHERE search_vector @@ query
ORDER BY relevance DESC;
-- 高亮显示
SELECT
id,
title,
ts_headline(
'chinese',
body,
plainto_tsquery('chinese', '索引优化'),
'StartSel=<em>, StopSel=</em>, MaxWords=50'
) AS excerpt
FROM zh_articles
WHERE search_vector @@ plainto_tsquery('chinese', '索引优化');

混合中英文搜索

-- 实际场景:文章里混有中英文(技术文档常见)
-- 策略:对标题用 chinese 配置,对代码/英文词用 simple 配置
CREATE OR REPLACE FUNCTION mixed_tsvector(title TEXT, body TEXT)
RETURNS TSVECTOR AS $$
SELECT
setweight(to_tsvector('chinese', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('simple', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('chinese', COALESCE(body, '')), 'B') ||
setweight(to_tsvector('simple', COALESCE(body, '')), 'B');
$$ LANGUAGE SQL IMMUTABLE;
-- 搜索时同样使用混合 tsquery
CREATE OR REPLACE FUNCTION mixed_tsquery(q TEXT)
RETURNS TSQUERY AS $$
SELECT
to_tsquery('chinese', q) ||
to_tsquery('simple', q);
$$ LANGUAGE SQL IMMUTABLE;
-- 查询
SELECT id, title
FROM zh_articles
WHERE mixed_tsvector(title, body) @@ mixed_tsquery('postgresql');
-- 能同时匹配中文"PostgreSQL"和英文"postgresql"

替代方案:zhparser(另一个中文分词扩展)

-- zhparser 基于 SCWS 分词,是另一个流行的选择
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION zhcfg (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION zhcfg
ADD MAPPING FOR n, v, a, i, e, l WITH simple;
-- 用法与 pg_jieba 相同,替换配置名即可
SELECT to_tsvector('zhcfg', '数据库性能优化');

无 pg_jieba 的备选方案

如果无法安装扩展,可以退化到以下方案:

-- 方案一:按字符 n-gram 索引(不需要分词)
-- 把文本拆成 2-gram 或 3-gram 存入 tsvector
CREATE OR REPLACE FUNCTION chinese_ngram(text TEXT, n INTEGER DEFAULT 2)
RETURNS TSVECTOR AS $$
DECLARE
result TSVECTOR := ''::TSVECTOR;
len INTEGER;
i INTEGER;
BEGIN
len := char_length(text);
FOR i IN 1..(len - n + 1) LOOP
result := result || to_tsvector('simple', substring(text FROM i FOR n));
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 搜索"性能优化"会被拆成"性能"、"能优"、"优化"
-- 只需要搜索词的任何 n-gram 存在即可
-- 方案二:使用 pg_trgm(英文和拼音化的中文有效)
CREATE EXTENSION pg_trgm;
CREATE INDEX idx_articles_trgm ON articles USING GIN (title gin_trgm_ops);
SELECT * FROM articles WHERE title % '性能优化';  -- 相似度搜索

性能对比

-- 测试全文搜索性能(100 万行)
EXPLAIN ANALYZE
SELECT id, title
FROM zh_articles
WHERE search_vector @@ plainto_tsquery('chinese', '数据库索引');
-- 走 GIN 索引时:
-- Bitmap Index Scan on idx_zh_articles_search
-- actual time = 1.2ms  ✅
-- vs LIKE 查询:
-- Seq Scan on zh_articles
-- actual time = 3200ms  ❌ 慢 2600 倍

下一节pgvector 向量相似度搜索——传统全文搜索依赖关键词匹配,pgvector 让 PostgreSQL 存储 AI 嵌入向量,实现语义相似度搜索——即使搜索词和文档中的词不完全一致,也能找到语义相关的结果。