Elasticsearch 的倒排索引与中文分词

米阳 2024-6-8 4 6/8

用 ES 做搜索,最常见的两个问题是「搜不到」和「搜出一堆不相关的」。这两个问题的根源往往都在分词和索引设计上。

倒排索引是什么

普通数据库是「文档 → 包含哪些词」,倒排索引反过来:「词 → 出现在哪些文档」。查「数据库」时直接拿到包含这个词的文档列表,不需要逐篇扫描。

词项        文档 ID
数据库      [1, 5, 9]
索引        [5, 9]
优化        [1, 9]

写入时把文本切成词项(分词),查询时把查询串也切成词项,然后求交集。所以分词规则必须一致——索引时怎么切,查询时就得怎么切。

中文为什么要单独处理

英文按空格切就行,中文没有空格。默认的 standard 分词器会把一整句中文切成单字,导致「数据库」被拆成「数」「据」「库」,搜「数据」也能命中,但相关性很差。

用 IK 分词器:

PUT /articles
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ik_smart_analyzer": { "type": "custom", "tokenizer": "ik_smart" }
      }
    }
  },
  "mappings": {
    "properties": {
      "title":   { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" },
      "content": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" },
      "created": { "type": "date" },
      "views":   { "type": "integer" }
    }
  }
}

关键点:索引时用 ik_max_word(细粒度,召回全),查询时用 ik_smart(粗粒度,更精准)。这个组合是中文搜索的常规配置。

验证分词结果

GET /articles/_analyze
{ "analyzer": "ik_max_word", "text": "MySQL 索引优化实战" }

输出会告诉你切成哪些词。搜不到的时候先跑这个,八成能看出问题。

相关性

ES 默认用 BM25 打分,考虑词频、逆文档频率、字段长度。标题和正文权重不同,用 multi_match 加 boost:

{
  "query": {
    "multi_match": {
      "query": "索引优化",
      "fields": ["title^3", "content"],
      "type": "best_fields"
    }
  }
}

title^3 表示标题命中权重是正文的三倍。

常见坑

  1. mapping 不能改。字段类型定了就不能改,要改只能重建索引 + reindex。所以一开始就把 mapping 想清楚,别靠动态映射。
  2. 深分页。from + size 超过 10000 会报错,用 search_after 或 scroll。
  3. 聚合字段用 keyword。text 字段不能直接聚合,要加 .keyword 子字段或者单独建 keyword 类型。
  4. 不要当主存储。ES 的数据可靠性不如数据库,丢了不好恢复,主数据必须在 MySQL 里。

一句话

ES 好用,但它不是「装了就能搜」。分词器、mapping、查询方式三样配好,才有符合预期的效果。

- THE END -

米阳

6月08日16:20

最后修改:2024年6月8日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论