很多刚开始学习 Elasticsearch 的人经常会混淆Text 和Keyword数据类型。 它们之间的区别很简单,但非常关键。
不同点
它们之间的本质区别在于:对于Text
类型,将文本存储到倒排索引之前,会使用分析器对其进行分析,而Keyword
类型则不会分析。文档是否被分析过会影响其查询的结果。关于倒排索引和分析器的内容可以参考:https://www.jianshu.com/p/04d29098851a
如何选择字段类型
如果将包含字符串的文档添加到 Elasticsearch,而之前没有定义字段的映射关系,那么 Elasticsearch 会自动创建一个包含Text
和Keyword
类型的动态映射。 即使它适用于动态映射,也建议在文档添加之前定义索引的映射关系,以节省空间并提高写入速度。
如:未定义mapping,直接添加文档内容,发现"message”的数据类型是text
,“message.keyword"的数据类型是keyword
。
PUT /demo/_doc/1
{
"message":"this is a message"
}
#查看索引的mapping信息
GET /demo/_mapping
返回mapping的结果
{
"demo" : {
"mappings" : {
"properties" : {
"message" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
现在新建一个索引text-vs-keyword
,并设置其mapping,keyword_field
字段设置为keyword
类型,text_field
字段设置为text
类型,text_and_keyword_mapping
为多字段类型,其本身为text
类型,而text_and_keyword_mapping.keyword
字段为keyword
类型。
#新建索引
PUT /text-vs-keyword
#设置索引mapping
PUT /text-vs-keyword/_mapping
{
"properties": {
"keyword_field": {
"type": "keyword"
},
"text_field": {
"type": "text"
},
"text_and_keyword_mapping": {
"type": "text",
"fields": {
"keyword_type": {
"type": "keyword"
}
}
}
}
}
工作原理
这两种字段类型在倒排索引中的存储方式不同,索引过程中的差异会影响Elasticsearch 进行查询的时间。
首先添加一条文档
POST /text-vs-keyword/_doc/example
{
"keyword_field": "The quick brown fox jumps over the lazy dog",
"text_field": "The quick brown fox jumps over the lazy dog"
}
添加后,索引中便会有一条文档
{
"_index": "text-vs-keyword",
"_type": "_doc",
"_id": "example",
"_score": 1.0,
"_source": {
"keyword_field": "The quick brown fox jumps over the lazy dog",
"text_field": "The quick brown fox jumps over the lazy dog"
}
}
Keyword类型
对于keyword
类型,由于Elasticsearch不会使用分析器对其进行分析,所以你输入什么文本,索引就会按照原样进行保存。下图为文本在倒排索引中存储的样子。
Text类型
对于text
类型,Elasticsearch会先使用分析器对文本进行分析,再存储到倒排索引中。Elasticsearch默认使用标准分析器(standard analyzer),先对文本分词再转化为小写。关于标准分析器可以参考先前文章:https://www.jianshu.com/p/04d29098851a
标准分析器对文本进行分析后的结果
POST /text-vs-keyword/_analyze
{
"analyzer": "standard",
"text": "The quick brown fox jumps over the lazy dog"
}
#响应结果
{
"tokens" : [
{
"token" : "the",
"start_offset" : 0,
"end_offset" : 3,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "quick",
"start_offset" : 4,
"end_offset" : 9,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "brown",
"start_offset" : 10,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "fox",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "jumps",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 4
},
{
"token" : "over",
"start_offset" : 26,
"end_offset" : 30,
"type" : "<ALPHANUM>",
"position" : 5
},
{
"token" : "the",
"start_offset" : 31,
"end_offset" : 34,
"type" : "<ALPHANUM>",
"position" : 6
},
{
"token" : "lazy",
"start_offset" : 35,
"end_offset" : 39,
"type" : "<ALPHANUM>",
"position" : 7
},
{
"token" : "dog",
"start_offset" : 40,
"end_offset" : 43,
"type" : "<ALPHANUM>",
"position" : 8
}
]
}
根据分析后的结果,下图为分词存储在倒排索引中的样子。
Text和Keyword的查询区别
现在已经知道了Text
和Keyword
存储在倒排索引中的区别,接下来学习他们在查询中的区别。
查询分为两种查询
- Match Query
- Term Query
Match Query
和Term Query
的区别与Text
和Keyword
的区别类似,Match Query
在查询时会对输入的关键词进行分析,而Term Query
不会。
Elasticsearch 的查询原理是将查询的关键词与倒排索引中的词条进行匹配,查询的关键词与倒排索引中的词条必须完全相同视为匹配,否则不匹配。 这意味着在插入文档时是否进行分析和查询时是否进行分析将产生非常不同的结果。
不同的字段和不同的查询一共可以产生4种情况:以X代表不分析,O代表分析,左侧代表插入文档时是否分析,右侧代表查询时是否分析,则OX代表插入时分析,查询时不分析。
Term Query | Match Query | |
---|---|---|
keyword | XX | XO |
text | OX | OO |
使用Term Query查询keyword字段
由于插入文档的keyword
字段和Term Query
查询时都不会进行分析,因此只有当文本完全匹配才会返回结果。
GET /text-vs-keyword/_search
{
"query": {
"term": {
"keyword_field": {
"value": "The quick brown fox jumps over the lazy dog"
}
}
}
}
#返回结果
"hits": [{
"_index": "text-vs-keyword",
"_type": "_doc",
"_id": "example",
"_score": 0.2876821,
"_source": {
"keyword_field": "The quick brown fox jumps over the lazy dog",
"text_field": "The quick brown fox jumps over the lazy dog"
}
}]
如果尝试文档中的一些词,由于不能与整篇文档相匹配,也不会返回结果。
GET /text-vs-keyword/_search
{
"query": {
"term": {
"keyword_field": {
"value": "The"
}
}
}
}
使用Match Query查询keyword字段
GET /text-vs-keyword/_search
{
"query": {
"match": {
"keyword_field": "The quick brown fox jumps over the lazy dog"
}
}
}
#返回结果
"hits": [{
"_index": "text-vs-keyword",
"_type": "_doc",
"_id": "example",
"_score": 0.2876821,
"_source": {
"keyword_field": "The quick brown fox jumps over the lazy dog",
"text_field": "The quick brown fox jumps over the lazy dog"
}
}]
这里有个疑问,查询关键词分析后的各个分词与倒排索引中的“The quick brown fox jumps over the lazy dog”不完全匹配,但为什么会产生结果呢?
这是因为我们在Match Query
查询时使用的分析器不是标准分析器,Elasticsearch使用了关键词分析器(Keyword Analyzer),因此 Elasticsearch 在查询中没有任何改变。更多分析器可以参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
查看Keyword Analyzer分析后的结果,发现并没有对搜索关键词进行分词。
GET /text-vs-keyword/_analyze
{
"text": "The quick brown fox jumps over the lazy dog",
"analyzer": "keyword"
}
#返回结果
{
"token": "The quick brown fox jumps over the lazy dog",
"start_offset": 0,
"end_offset": 43,
"type": "word",
"position": 0
}
当选择标准分词器时进行Match Query
查询时将不会得到任何结果。
GET /text-vs-keyword/_search
{
"query": {
"match": {
"keyword_field": {
"query": "The quick brown fox jumps over the lazy dog",
"analyzer": "standard"
}
}
}
}
使用Term Query查询text字段
text
字段在文档插入时会对文本进行分析,得到若干个分词。
尝试两条查询语句,一条查询关键词为完整的句子,另一条只有一个单词。
GET /text-vs-keyword/_search
{
"query": {
"term": {
"text_field": {
"value": "The quick brown fox jumps over the lazy dog"
}
}
}
}
GET /text-vs-keyword/_search
{
"query": {
"term": {
"text_field": {
"value": "The"
}
}
}
}
两条语句都返回空结果:
- 第一个查询没有产生任何结果,因为经过分析器分析后,在倒排索引中,从未存储整个句子,索引过程只存储分析后的分词。
- 第二个查询也没有产生任何结果。 索引关键字为“The”,而标准分析器中的小写字母过滤器会将分词转化为小写,因此在倒排索引中,它存储的时“the”。
将查询单词转换为小写试下,由于查询关键字"the"与倒排索引中的"the"正好匹配,所以会返回该条记录。
GET /text-vs-keyword/_search
{
"query": {
"term": {
"text_field": {
"value": "the"
}
}
}
}
#返回结果
"hits": [{
"_index": "text-vs-keyword",
"_type": "_doc",
"_id": "example",
"_score": 0.39556286,
"_source": {
"keyword_field": "The quick brown fox jumps over the lazy dog",
"text_field": "The quick brown fox jumps over the lazy dog"
}
}]
使用Match Query查询text字段
text
字段和Match Query查询都会进行分析。
下面尝试两条查询语句
GET /text-vs-keyword/_search
{
"query": {
"match": {
"text_field": "The"
}
}
}
GET /text-vs-keyword/_search
{
"query": {
"match": {
"text_field": "the LAZ dog tripped over th QUICK brown dog"
}
}
}
这两条查询语句都返回了结果:
- 第一个查询产生了一个结果,因为查询关键字中的“The”被分析为“the”,它与倒排索引中存储的完全匹配。
- 第二个查询虽然并非所有分词都在倒排索引中,但仍会产生结果。因为只要查询的一个分词与倒排索引中的分词完全匹配,Elasticsearch 仍会返回这个结果。
如何选择这两个数据类型
以下情况推荐使用keyword
类型:
- 需要完全匹配的查询
- 需要用于通配符查询
以下情况推荐使用text
类型: - 希望使用自动补全的功能
- 希望使用搜索系统
结论
掌握text
和keyword
数据类型的工作原理是学习Elasticsearch的内容之一,这两者的区别似乎很简单,但非常重要。如果需要两种数据类型,则可以在创建映射时使用多字段功能。