跳到主要内容

全文搜索介绍

全文搜索(full-text search)是针对长文本进行“按词查找、相关性排序”的搜索能力。它先把文本分析成词项,再通过倒排索引快速找到包含这些词项的记录,并根据匹配程度计算相关性。

全文搜索适合文章、商品描述、知识库、评论等自然语言内容。它和普通的 LIKE 模糊匹配不是同一种能力:LIKE 主要比较字符串片段,全文搜索则理解词、词组和匹配权重。具体语法、分词器、停用词和索引参数都依赖数据库产品与版本,本文先建立通用模型,再给出常见实现的示例。

先区分 LIKE 和全文搜索​

假设 articles 表有 title 和 body 两列。最直接的搜索方式是:

SELECT id, title
FROM articles
WHERE title LIKE '%database%'
OR body LIKE '%database%';

这种写法有几个局限:

  • '%database%' 通常不能直接利用普通 B-tree 索引,数据量大时可能需要扫描很多行;
  • 它只知道字符是否出现,不知道 database 是一个词还是更长字符串的一部分;
  • 多个关键词、词组、同义词、词形变化和结果相关性需要应用层额外实现;
  • 不同数据库的大小写、重音符号和排序规则会影响匹配结果。

全文搜索通常可以表达类似这样的需求:

查找标题或正文中同时出现“数据库”和“索引”的文章,完整出现“全文搜索”这一词组的结果优先,并按相关性从高到低排列。

如果需求只是查找短编码、订单号或前缀,LIKE、= 或普通索引往往更合适。不要把“包含某段字符”的所有问题都换成全文搜索。

全文搜索是怎样工作的​

全文搜索通常包含四个阶段。

1. 文本分析​

数据库会对原文进行规范化和分词,常见步骤包括:

  • 把字母统一为小写,或按配置进行大小写折叠;
  • 按空格、标点或语言规则切分词语;
  • 去掉没有检索价值的停用词,例如某些语言中的“the”“and”;
  • 对词进行词干化或词形归一,例如把 running 归到 run;
  • 对中文、日文等没有空格分隔的语言使用专门的分词器。

同一套分析规则必须同时用于建立索引和解析查询。索引使用英文词干化,而查询使用不带词干化的原始字符串,可能导致本应匹配的结果缺失。

2. 建立倒排索引​

全文索引常使用倒排索引(inverted index)。可以把它理解为从“词”反查“文档”的目录:

词项包含它的文档额外信息示例
database1、4、9出现次数、位置、字段
index1、2、9出现次数、位置、字段

查询 database index 时,搜索引擎先分别找到两个词项对应的文档集合,再按照 AND、OR、词组或其他规则合并。索引中保存词项的位置和字段信息后,还可以判断相邻词组、标题命中和词频。

全文索引不是普通 B-tree 的另一种写法。它有自己的存储结构、更新机制和配置,不能仅通过给文本列建立普通索引来获得全文搜索能力。

3. 解析查询​

用户输入的搜索词也需要经过分析器处理。数据库可能支持以下查询语义:

  • 自然语言模式:把输入当作自然语言,根据相关性排序;
  • 布尔模式:允许 AND、OR、排除词、必须出现等操作;
  • 词组搜索:要求多个词按指定顺序相邻出现;
  • 前缀搜索:匹配以某个词根开头的词,但会扩大搜索范围;
  • 字段加权:标题命中的权重高于正文命中。

查询语法不是 SQL 标准的一部分。同一段输入在不同数据库中可能有不同含义,应用层应明确选择模式并验证用户输入,而不是把用户字符串直接拼接进查询语法。

4. 计算相关性​

全文搜索可以返回“匹配程度”而不只是匹配或不匹配。常见影响因素包括:

  • 词在文档中出现的次数;
  • 词在整个语料库中的稀有程度;
  • 命中标题、标签还是正文;
  • 多个词是否都出现、是否组成完整词组;
  • 词在文档中的位置和距离。

相关性分数只是排序依据,不是业务事实。数据量、分析器和数据库版本变化后,分数的绝对值可能变化,不应把它当作跨环境稳定的业务指标。

一个通用的搜索流程​

可以把一次全文搜索拆成下面几步:

用户输入
-> 校验长度和语义
-> 按数据库的查询规则解析
-> 使用全文索引查找候选记录
-> 计算相关性并排序
-> 按稳定的第二排序键分页
-> 返回摘要或高亮片段

例如,伪 SQL 可以表达为:

SELECT id, title, relevance
FROM search_articles(:query)
ORDER BY relevance DESC, id DESC
FETCH FIRST 20 ROWS ONLY;

search_articles 只是表示数据库特定的全文搜索表达式,并非通用 SQL 函数。分页时建议在相关性之外增加唯一键等稳定排序键,避免分数相同时记录在不同页面之间跳动。

常见数据库实现​

下面的代码只展示思路。执行前请核对目标数据库和版本的文档,尤其是分词器、停用词和索引创建选项。

PostgreSQL:tsvector、tsquery 和 GIN​

PostgreSQL 通常把待搜索文本转换为 tsvector,把查询转换为 tsquery,再使用 GIN 或 GiST 索引。示例使用 simple 配置,它不会替你完成特定语言的词干化:

-- 直接查询表达式,适合说明原理
SELECT id, title,
ts_rank(
to_tsvector('simple', concat_ws(' ', title, body)),
websearch_to_tsquery('simple', :query)
) AS relevance
FROM articles
WHERE to_tsvector('simple', concat_ws(' ', title, body))
@@ websearch_to_tsquery('simple', :query)
ORDER BY relevance DESC, id DESC;

生产环境通常会把向量物化或使用生成列,并为它建立索引,避免每次查询都重新分析全文:

ALTER TABLE articles
ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(title, '')), 'A') ||
setweight(to_tsvector('simple', coalesce(body, '')), 'B')
) STORED;

CREATE INDEX idx_articles_search_vector
ON articles USING GIN (search_vector);

这里用 A、B 区分字段权重,标题命中可以比正文命中更重要。websearch_to_tsquery 更接近用户常见的搜索输入;to_tsquery 支持更严格的数据库查询语法,但不应直接接收未经处理的用户输入。

PostgreSQL 内置配置对中文分词未必满足业务要求。中文项目可能需要扩展、外部搜索服务或在应用层完成分词后再设计索引,不能仅把 simple 改成任意语言名称就假设中文搜索已经可用。

MySQL:FULLTEXT 和 MATCH ... AGAINST​

MySQL 可以为文本列建立 FULLTEXT 索引,并使用 MATCH ... AGAINST 搜索:

CREATE FULLTEXT INDEX ftx_articles_title_body
ON articles (title, body);

SELECT id, title,
MATCH(title, body) AGAINST (:query IN NATURAL LANGUAGE MODE) AS relevance
FROM articles
WHERE MATCH(title, body) AGAINST (:query IN NATURAL LANGUAGE MODE) > 0
ORDER BY relevance DESC, id DESC;

布尔模式可以表达必须出现、排除或前缀等条件,例如:

SELECT id, title
FROM articles
WHERE MATCH(title, body)
AGAINST ('+database -legacy index*' IN BOOLEAN MODE);

搜索词中的操作符是 MySQL 布尔查询语法的一部分,不能把未经校验的用户输入直接拼入 AGAINST 字符串。还要关注 InnoDB 的最小词长、停用词表和字符集设置;这些配置会改变哪些词能进入索引。中文通常需要 ngram 等分词配置,具体参数和效果必须结合版本和数据验证。

SQL Server:全文目录与谓词​

SQL Server 使用全文目录和全文索引保存文本数据,常见查询谓词包括:

SELECT a.id, a.title
FROM dbo.articles AS a
WHERE CONTAINS((a.title, a.body), '"database index"');

CONTAINS 适合更精确的词、词组和布尔条件,FREETEXT 更偏向自然语言匹配。建立全文目录、选择语言和等待索引填充都属于管理步骤,不能只执行一条查询就假设索引立即可用。

SQLite:FTS5 虚拟表​

SQLite 常通过 FTS5 虚拟表提供全文搜索。它不是普通表的自动索引,应用需要决定如何同步原始表和 FTS 表:

CREATE VIRTUAL TABLE article_search USING fts5(
title,
body,
content='articles',
content_rowid='id'
);

SELECT rowid, title
FROM article_search
WHERE article_search MATCH :query
ORDER BY rank;

content、触发器或应用写入流程必须保持搜索表与原始表一致。FTS5 的 tokenizer、前缀索引和高亮函数也有自己的配置,不应照搬其他数据库的语法。

设计全文搜索索引时要考虑什么​

搜索哪些字段​

不要把所有文本列无差别地塞进一个索引。先确定用户真正会搜索的字段,例如:

  • 标题、正文和摘要是否都可搜索;
  • 标签、作者名、分类是否使用精确筛选而不是全文匹配;
  • 草稿、已删除或无权访问的记录是否应该进入索引;
  • 标题是否需要比正文更高的权重。

权限过滤尤其重要。全文索引只能帮助定位候选记录,最终 WHERE 仍必须执行租户、发布状态和访问权限检查,不能因为“搜索索引里已经筛过”就省略授权条件。

处理空值和更新​

拼接多个字段时要明确处理 NULL,否则某些数据库中的字符串连接可能得到整个 NULL:

concat_ws(' ', coalesce(title, ''), coalesce(body, ''))

插入、更新和删除数据时,全文索引也要同步维护。不同实现可能采用即时更新、异步填充或后台合并,因此刚写入的数据未必立刻可搜索。对“发布后必须立即可见”的业务,应确认数据库的填充语义并设计补偿或状态提示。

选择分词和语言配置​

分词器决定搜索的基本边界。中文、日文、英文混合文本还会遇到:

  • 中文词边界没有空格,分词结果可能存在歧义;
  • 专有名词、产品型号和缩写可能被错误拆分;
  • 停用词表过大可能误删有业务意义的词;
  • 词干化可能提高召回率,但会降低精确匹配程度。

应使用真实业务文本建立测试集,至少覆盖短词、数字、标点、同义词、混合语言、空查询和极长查询。不要只用几条人工示例判断分词效果。

搜索质量:召回率与准确率​

全文搜索常见的两个目标是:

  • 召回率(recall):相关结果尽量不要漏掉;
  • 准确率(precision):返回的结果尽量相关,减少噪声。

例如,把词拆得很细可能提高召回率,却让很多不相关文档进入结果;设置严格的完整词组匹配可能提高准确率,却漏掉词形变化或同义表达。搜索功能通常需要通过字段权重、同义词、分词规则、过滤条件和排序公式做平衡。

LIKE '%关键词%'、全文搜索和专门搜索引擎解决的问题也不同:

需求常见选择原因
主键、编码、邮箱精确查找= 或普通索引语义明确,索引简单
短文本前缀查找LIKE 'prefix%' 或数据库前缀索引不需要自然语言分析
中小规模文章的词搜索数据库全文索引部署简单,事务数据与搜索数据较近
拼写纠错、复杂聚合、跨库搜索专门搜索引擎或扩展提供更丰富的分析和分布式能力

模糊匹配、相似度搜索、向量检索和全文搜索不是同义词。拼写纠错可能需要编辑距离,语义相似度可能需要向量索引;全文索引本身不能自动提供这些能力。

性能、分页与维护​

查询性能​

  • 搜索列和查询表达式必须与全文索引的定义一致,否则数据库可能退化为扫描或无法使用索引;
  • 只返回需要的字段,先应用租户、状态、时间等过滤条件;
  • 对相关性排序、计数和高亮要观察执行计划和响应时间;
  • 限制空搜索、超长查询和一次返回的结果数,避免用户输入触发昂贵查询;
  • 使用数据库的 EXPLAIN 或等价工具验证实际计划,不要凭 SQL 外观判断是否走了全文索引。

分页的稳定性​

只按相关性排序时,大量记录可能具有相同分数。应增加唯一键作为第二排序条件:

ORDER BY relevance DESC, id DESC

OFFSET 越大,数据库可能需要跳过越多候选行。高流量或深分页场景可以考虑基于上一次结果的游标分页,但游标中需要保存与排序一致的分数、唯一键或搜索引擎提供的稳定标记。具体实现取决于数据库的全文查询能力。

索引维护​

全文索引会占用磁盘和内存,并增加写入、批量导入、备份和重建成本。上线前后应关注:

  1. 建索引和重建索引是否锁表或消耗大量 I/O;
  2. 大批量导入时,是先导入后建索引,还是分批维护索引;
  3. 数据更新后索引多久可见,是否存在失败重试或积压;
  4. 停用词、分词器或词长配置改变后是否需要重建索引;
  5. 索引大小、查询延迟、命中率和结果质量是否达到目标。

安全和正确性边界​

  • 使用驱动的参数绑定传递普通查询值,防止 SQL 注入;
  • 若数据库全文模式有额外操作符,先按业务允许的语法解析或转义用户输入;
  • 始终保留权限、租户和发布状态条件,全文搜索不能替代授权;
  • 明确空字符串、只有停用词、只有标点和超长输入的行为;
  • 搜索结果展示时,对标题、摘要和高亮片段进行 HTML 转义,避免把原文当作 HTML 输出;
  • 不要依赖相关性分数判断“是否真的存在”,匹配规则和阈值应由目标数据库和业务测试确定。

一套实用的落地流程​

  1. 收集真实搜索场景,区分精确查找、前缀查找、自然语言搜索和相似度搜索。
  2. 选择要搜索的字段、语言配置、停用词策略和字段权重。
  3. 根据目标数据库创建全文索引,明确同步、异步填充和失败重建机制。
  4. 用包含中文、英文、数字、专有名词和权限边界的测试集验证召回率与准确率。
  5. 使用执行计划和压测数据验证查询耗时、索引大小、写入开销及深分页表现。
  6. 对用户输入做长度限制、语法处理和参数化,对结果做授权过滤和输出转义。
  7. 上线后监控零结果查询、慢查询、索引积压和用户点击反馈,持续调整分词与排序。

记住三点:全文搜索依赖“文本分析 + 倒排索引 + 查询解析”,普通索引不能直接替代它;中文搜索效果首先取决于分词和词典;搜索结果必须同时满足相关性、性能和权限约束。