Jason Pan

ElasticSearch 中的 keyword

潘忠显 / 2025-07-15


在现代数据驱动的世界中,ElasticSearch 作为一个强大的分布式搜索和分析引擎,广泛应用于各种场景。它能够处理海量数据,并提供快速的搜索和分析能力。

如果你用过 ElasticSearch,很可能见过 keyword 这个单词。但是 keyword 在 ES 中有中含义,一种是作为一种数据类型,另一种是 text 类型中的字段名。这两种概念很容易混淆,本文就来介绍一下 keyword 作为类型和字段的不同用法,以及他们之间的关系。

keyword 作为数据类型

ElasticSearch 支持多种数据类型,包括字符串、数字、日期等,理解这些数据类型是有效使用 ElasticSearch 的关键。

elastic-search-logo

其中,字符串类型分为 text 和 keyword 两种:

比如下边这个图是腾讯云配置索引的操作界面:

ops-on-qcloud

keyword 类型的主要特性是不进行分词,这意味着它适用于需要精确匹配的场景。

由于不分词,keyword 类型在存储和索引时更加高效,适合用于过滤、聚合和排序等操作。

它通常用于存储结构化数据,如电子邮件地址、用户名、标签等。

keyword 作为字段

text 类型的字段会进行分词和分析,适合用于全文搜索。

然而,在某些情况下,我们可能需要对同一字段进行精确匹配

为此,ElasticSearch 提供了 fields 属性,允许在 text 类型中定义一个 keyword 子字段。这样,我们可以在同一字段上同时进行全文搜索和精确匹配。

text 类型的字段并不会默认创建一个 keyword 子字段。需要显式地在索引映射中定义这个子字段。


我们可以在腾讯云上实际创建前边图1中的索引。如果通过索引创建的图形界面,只能创建text_field的类型是个text,而没有子字段:

mappings-from-setting

如果要加入 keyword 字段,则可以切换到 JSON 模式,比如这里新增一个 text_key_word_field 字段,这里的 fields 是我手动输入的:

insert-text-with-keyword-field

以上配置中有一个 ignore_above 配置,可以避免索引过长的字符串作为是keyword,达到优化索引性能的效果。

此外,避免在不必要的字段上使用 text 类型,以减少索引大小和提高查询性能。

Kibana Dev Tools 上实际操作

通过 kibana dev tools 我们也可以直接通过 GET tt/_mapping 来查看索引的字段信息:

check-mappings-from-dev-tools

上述索引中,mapping有三个字段:

创建索引了之后,我们插入2条记录,以便后边进行测试:

POST /tt/_doc/1
{
  "keyword_field": "exact_match_value",
  "text_field": "This is a text field with full text search capabilities.",
  "text_key_word_field": "This field supports both full text search and exact match."
}
POST /tt/_doc/2
{
  "keyword_field": "another_exact_value",
  "text_field": "Another text field with different content.",
  "text_key_word_field": "Another field for testing both search types."
}

case1: 查询 keyword_field 的精确匹配(这个只会找到 id=1 的文档)

GET /tt/_search
{
  "query": {
    "term": {
      "keyword_field": "exact_match_value"
    }
  }
}

case2: 查询 text_field 的全文搜索(这个查询结果包含 id=1和id=2的文档,因为第一个文档包含了“full text search”,而第二个文档包含了“text”)

GET /tt/_search
{
  "query": {
    "match": {
      "text_field": "full text search"
    }
  }
}

case3: 查询 text_key_word_field 的精确匹配(查询结果只包括 id=1 的文档)

GET /tt/_search
{
  "query": {
    "term": {
      "text_key_word_field.keyword": "This field supports both full text search and exact match."
    }
  }
}

case4: 查询 text_key_word_field 的全文搜索(查询结果包括id=1和id=2文档)

GET /tt/_search
{
  "query": {
    "match": {
      "text_key_word_field": "supports both search"
    }
  }
}

case5: 查询 text_field 的「精确匹配」(查询结果为空,因为 text_filed 并没有 keyword 字段,不会报错)

GET /tt/_search
{
  "query": {
    "match": {
      "text_field.keyword": "This is a text field with full text search capabilities."
    }
  }
}

case6: 误用 keyword_field 的精确匹配(查询结果为空,因为 keyword 并没有 keyword 字段,不会报错)

GET /tt/_search
{
  "query": {
    "term": {
      "keyword_field.keyword": "exact_match_value"
    }
  }
}

通过上边的5个测试,我们得到了以下结论:

全文搜索与匹配打分

上边的 case 2 和 case 4 都是使用了全文搜索。我们可以看看 case 2 中具体的返回结果:

query-result-with-score

返回的 hits 数组中,除了 id 和 source 之外,还有一个 score。

我们搜 “full text search”,这三个单词在 id=1的 doc 中都有,分数是1.49;而在 id=2 的doc中只有 “text” 的单词,分数时0.2。

_score 字段在 ElasticSearch 的查询结果中表示文档与查询的匹配程度,它是一个浮点数,通常用于排序查询结果。分数越高,表示文档与查询的相关性越高

ElasticSearch 使用一种称为 BM25 的算法(以及其他可能的评分机制)来计算这个相关性分数。

bm25_equation

以下是一些影响 _score 的因素:

  1. 词频(Term Frequency, TF):在文档中某个词出现的次数越多,相关性越高,分数也越高。
  2. 逆文档频率(Inverse Document Frequency, IDF):某个词在整个索引中出现的次数越少,说明这个词更具辨识度,相关性越高,分数也越高。
  3. 字段长度规范化(Field Length Normalization):较短的字段更可能是相关的,因此在较短字段中出现的词会有更高的分数。
  4. 查询中的词项:查询中包含的词项越多,匹配的词项越多,分数越高。
  5. Boosting:查询可以携带 boost 参数,人为地提高某些字段或词项的权重,从而影响 _score 的计算。

需要注意的是,_score 的值是相对的,主要用于比较同一查询结果中的文档,在不同的查询中可能没有绝对的意义

小结

keyword 类型适用于需要精确匹配的场景,如过滤、聚合和排序。典型的应用包括日志分析中的状态码、用户 ID、标签等字段。

text 类型则适用于需要全文搜索的场景,如文章内容、评论、描述等。

text 类型字段增加 keyword 子字段可以支持精确匹配。

只有需要精确匹配 text 类型时,查询条件才加 “.keyword”,其他两种都不应该加。

text 类型全文搜索会对性能有一定的影响。当我们考虑减少索引大小和提高查询性能时,可以通过避免在不必要的字段上使用 text 类型、合理设置字段的 ignore_above 属性等方式进行改善。