Skip to content

中文分词和 IK 分词器

ES 做全文检索时,分词器非常重要。英文天然有空格,分词相对容易;中文没有天然空格,如果分词器不合适,经常会出现“明明有数据却搜不到”或者“搜出来一堆不相关结果”。

Analyzer 做了什么?

Analyzer 可以理解成文本进入倒排索引前的一条处理流水线。它通常包含:

  • character filter:先处理原始字符,比如去 HTML 标签。
  • tokenizer:把文本切成 token。
  • token filter:对 token 做小写、停用词、同义词等处理。

写入文档时,text 字段会通过 analyzer 被切成 term,写进倒排索引。

查询 match 时,查询词也会通过 analyzer 被切成 term,再去匹配倒排索引。

所以,写入分词和查询分词要匹配,否则就会影响召回。

standard 分词器为什么不适合中文?

ES 默认 standard analyzer 对英文效果不错,但对中文经常不能满足业务预期。

比如:

http
GET /_analyze
{
  "analyzer": "standard",
  "text": "中华人民共和国国歌"
}

不同版本下具体结果可能有差异,但它通常不能像中文搜索那样理解词语边界。

中文搜索更常用 IK、jieba、HanLP、smartcn 等分词方案。在国内业务里,IK 是很常见的选择。

IK 的两个常用分词器

IK 插件常用两个 analyzer:

  • ik_max_word:细粒度拆分,尽可能多地切出词,召回更高。
  • ik_smart:粗粒度拆分,切出更少更长的词,结果更精确。

例如“中华人民共和国国歌”:

  • ik_max_word 可能切出:中华人民共和国、中华人民、中华、人民、共和国、国歌等。
  • ik_smart 可能切出:中华人民共和国、国歌。

常见设计是:

json
{
  "title": {
    "type": "text",
    "analyzer": "ik_max_word",
    "search_analyzer": "ik_smart"
  }
}

写入时用细粒度,提高召回;查询时用粗粒度,减少噪音。

安装 IK 要注意版本匹配

IK 插件版本要和 ES 版本匹配。比如 ES 是 7.17.x,就要选择对应的 IK 7.17.x 插件。

常见安装方式:

bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.0/elasticsearch-analysis-ik-7.17.0.zip

安装完成后需要重启 ES 节点。集群环境中,每个需要处理该索引分片的节点都要安装同版本插件,否则分片可能无法正常分配或查询失败。

验证插件:

http
GET /_cat/plugins?v

测试分词:

http
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "金十期货"
}

在 Mapping 里使用 IK

创建索引时指定 analyzer:

http
PUT /articles
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        "fields": {
          "keyword": { "type": "keyword", "ignore_above": 256 }
        }
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

如果索引已经创建,已有字段的 analyzer 通常不能直接改。需要新建索引并 reindex。

自定义词典

业务词对中文搜索特别重要。

比如“德玛西亚”“AIGC”“金十期货”这类词,如果默认词典不认识,就可能被拆得很碎,搜索效果变差。

IK 支持本地扩展词典。常见步骤:

  1. plugins/ik/config 下创建自定义词典文件,比如 custom.dic
  2. 每行写一个词。
  3. IKAnalyzer.cfg.xml 里配置扩展词典。
  4. 重启 ES。

词典内容示例:

text
德玛西亚
金十期货
Elasticsearch

重启后用 _analyze 验证:

http
GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "金十期货发布消息"
}

远程词典热更新

生产环境频繁重启 ES 不现实,所以 IK 也支持远程词典。

远程词典接口需要满足两个条件:

  • HTTP 响应带 Last-ModifiedETag 头。
  • 响应内容一行一个词,使用换行分隔。

可以用 Nginx 托管一个 txt 文件,也可以由内部词库服务提供接口。

配置示意:

xml
<entry key="remote_ext_dict">http://dict.example.com/ik_ext.dic</entry>

Last-ModifiedETag 变化时,IK 会重新拉取词典。这样新增词可以不重启 ES 生效。

词典更新后,历史数据会自动变化吗?

不会。

词典更新只影响之后的分词行为。已经写入索引的历史文档,它们的倒排索引还是旧分词结果。

如果希望历史数据也按新词典重新分词,有两种常见方式:

使用 _update_by_query 触发重新索引当前索引里的文档:

http
POST /articles/_update_by_query?conflicts=proceed

或者创建新索引后 _reindex

http
POST /_reindex
{
  "source": { "index": "articles" },
  "dest": { "index": "articles_v2" }
}

如果 Mapping、分词器、字段结构都要调整,更推荐新建索引再 reindex,最后用别名切换。

搜索效果怎么调?

分词只是搜索效果的一部分。调搜索时可以按这个顺序看:

  1. _analyze 看标题、正文、查询词分别被切成什么。
  2. 确认字段是 text 还是 keyword。
  3. title、content 等字段是否需要不同 boost。
  4. 业务词有没有进入扩展词典。
  5. 是否需要同义词,比如“ES”和“Elasticsearch”。
  6. 是否需要 match_phrase 提高短语匹配权重。
  7. 是否需要 function_score 叠加时间、热度、标签权重。

例如让标题权重更高:

http
GET /articles/_search
{
  "query": {
    "multi_match": {
      "query": "Elasticsearch 写入",
      "fields": ["title^3", "content"]
    }
  }
}

停用词和同义词

一些高频无意义词,比如“的”“了”“啊”,可以考虑停用词。但中文搜索里停用词要谨慎,过度过滤可能影响短句查询。

同义词也很常见,比如:

text
ES, Elasticsearch
数据库, DB
全文检索, 搜索

同义词会影响召回和相关性,最好有明确的业务维护流程,不要随手堆词。词越多,误召回也可能越多。

小结

中文搜索的关键不是“装了 IK 就万事大吉”,而是要形成完整链路:

  • Mapping 里正确声明 text 字段和 analyzer。
  • 写入分词和查询分词要匹配。
  • 业务词要进入扩展词典。
  • 词典更新后,历史索引需要重新索引才会改变。
  • 搜索效果要结合字段权重、短语匹配、同义词和业务排序一起调。

只要把分词结果看清楚,很多 ES 搜索问题就会从玄学变成工程问题。