静态映射
静态映射是在创建索引时手工指定索引映射,和 SQL 在建表语句中指定字段属性类似。相比动态映射,通过静态映射可以添加更详细、更精准的配置信息。
1. 元字段
每个文档都有与之关联的元数据,用来描述文档本身的字段,例如 _index、_type、_id 等元字段。_type 元字段已经在 ES 6.0 版本之后被废除,下面我们来详细介绍下各个元字段。
1.1 _index
在多索引查询时,有时需要添加仅与某些索引的文档相关联的查询子句时,_index 字段提供了便利。_index 支持对索引名进行 term 查询、terms 查询、聚合分析、使用脚本和排序。注意 _index 只是一个虚拟字段,并不会真的加到 Lucene 索引中。
比如有 index_1 和 index_2 这两个索引:
curl -X PUT "localhost:9200/index_1/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"text": "Document in index 1"
}'
curl -X PUT "localhost:9200/index_2/_doc/2?refresh=true&pretty" -H 'Content-Type: application/json' -d'
{
"text": "Document in index 2"
}'
# 按照索引名进行搜索、分组聚合和排序
curl -X GET "localhost:9200/index_1,index_2/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"terms": {
"_index": ["index_1", "index_2"]
}
},
"aggs": {
"indices": {
"terms": {
"field": "_index",
"size": 10
}
}
},
"sort": [
{
"_index": {
"order": "asc"
}
}
]
}'
1.2 _id
每个文档都有一个唯一标识它的 _id 元字段,该字段会被索引以便可以使用 GET API 或 ids query 进行查询。该字段可用于 term、terms、match、query_string、simple_query_string 查询,最好不要用于聚合和排序,因为它需要在内存中加载大量数据。如果需要对 _id 字段进行排序或聚合,可以复制 _id 字段的内容到文档的另一个字段中。_id 字段的最大长度为 512 字节,更大的值将会被拒绝。
查询 my_index 索引下 id 为 1 和 2 的文档,示例如下:
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"terms": {
"_id": [ "1", "2" ]
}
}
}'
1.3 _source
_source 字段包含在写入文档时传入的原始 JSON 文档主体,该字段本身没有被索引,因此是不可搜索的,但它会被存储,以便在执行 get 或 search 请求时返回。默认 _source 字段是开启的,也可以在 Mapping 定义中通过 enabled 参数关闭:
curl -X PUT "localhost:9200/tweets?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"_source": {
"enabled": false
}
}
}'
如果手动关闭了 _source 字段,那么很多特性将不再受到支持,比如:update、update_by_query、reindex、关键字高亮、排序、聚合等,这些诸多操作都需要用到 _source 字段中存储的原始 JSON 文档。因此如果是因为存储空间而禁用掉 _source 字段的话,请提高压缩级别而不是禁用掉该字段。
1.4 _size
_size 用于描述文档本身的字节大小,默认是不支持的。如果有统计文档大小的需求,需要安装 mapper-size 插件,该插件会以字节为单位索引原始 _source 字段的大小。安装命令如下:
bin/elasticsearch-plugin install mapper-size
该插件必须安装在集群中的每个节点上,并且每个节点安装后必须重新启动。然后在 Mapping 定义中开启 _size 字段才可以使用:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"_size": {
"enabled": true
}
}
}'
_size 字段的值可以在查询、聚合和排序时访问:
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"range": {
"_size": { "gt": 10 }
}
},
"aggs": {
"sizes": {
"terms": {
"field": "_size",
"size": 10
}
}
},
"sort": [
{
"_size": { "order": "desc" }
}
]
}'
1.5 _field_names
_field_names 字段用于索引文档中包含除 null 以外的任何值的每个字段的名称,exists 查询使用此字段来查找具有或不具有特定字段的任何非空值的文档。
通常我们不需要禁用掉 _field_names 字段,因为它没有索引开销。但如果你有很多字段都禁用掉了 doc_value 和 norms 属性,并且你也不使用 exists 查询。则可以通过如下设置禁用掉该字段:
curl -X PUT "localhost:9200/tweets?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"_field_names": {
"enabled": false
}
}
}'
1.6 _routing
_routing 字段用于计算文档应该路由到索引中的哪个分片上,Elasticsearch 通过以下公式进行计算:
shard_num = hash(_routing) % num_primary_shards
默认的 _routing 值是文档的 _id,通过 _routing 参数可以设置自定义路由,这样当执行搜索请求时不需要将查询分散到索引中的所有分片,而是发送到匹配特定路由值的分片上,减少了搜索的范围。
curl -X PUT "localhost:9200/my_index/_doc/1?routing=user1" -H 'Content-Type: application/json' -d'
{
"title": "This is a document"
}'
curl -X GET "localhost:9200/my_index/_search?routing=user1" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"title": "document"
}
}
}'
在使用自定义路由时,关键要在 indexing、getting、deleting 或 updating 文档时提供路由值。忘记提供路由值会导致文档在多个分片上被索引。作为保护措施,我们可以在 Mapping 定义中配置 _routing 字段为必须,使得在执行文档的所有 CRUD 操作时一定要传自定义路由值。
curl -X PUT "localhost:9200/my_index2?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"_routing": {
"required": true
}
}
}'
指定文档的路由值为必需的之后,索引文档必须提供路由参数,否则就会报错。
2. 映射参数
Elasticsearch 提供了足够多的映射参数对字段的映射进行参数设置,一些常用功能的实现,比如字段的分词器、字段的权重、日期格式、检索模型的选择等都是通过映射参数来配置完成,下面详细介绍各参数的用法。
2.1 analyzer
analyzer 参数用于指定文本字段的分词器,对索引和查询都有效。分词器会把 text 字段类型的内容转换为若干个词项,查询时分词器同样把查询字符串通过和索引时期相同的分词器或者其他分词器进行解析。
以常用的 IK 中文分词器为例,对于 content 字段,analyzer 参数的取值为 ik_max_word,意味着 content 字段内容索引时和查询时都使用 ik_max_word 分词器进行分词,配置映射的命令如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}'
大多数情况下索引和搜索的时候应该指定相同的分词器,确保 query 解析以后和索引中的词项一致。但是有时候也需要指定不同的分词器,例如,使用 edge_ngram 过滤器实现自动补全。默认情况下查询会使用 analyzer 属性指定的分词器,但也可以被 search_analyzer 属性覆盖。使用示例如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"analysis": {
"filter": {
"autocomplete_filter": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 20
}
},
"analyzer": {
"autocomplete": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"autocomplete_filter"
]
}
}
}
},
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "autocomplete",
"search_analyzer": "standard"
}
}
}
}'
text 字段使用 autocomplete 分词器进行索引,但是使用 standard 分词器进行搜索。索引一条文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"text": "Quick Brown Fox"
}'
text 字段生成的倒排索引包含以下词项:
[q, qu, qui, quic, quick, b, br, bro, brow, brown, f, fo, fox]
2.2 normalizer
normalizer 参数用于解析前的标准化配置,比如把所有的字符转化为小写。下面的例子中 foo 字段的值在解析前使用自定义的 normalizer 把字符串标准化并转换为小写形式:
curl -X PUT "localhost:9200/index?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"analysis": {
"normalizer": {
"my_normalizer": {
"type": "custom",
"char_filter": [],
"filter": ["lowercase", "asciifolding"]
}
}
}
},
"mappings": {
"properties": {
"foo": {
"type": "keyword",
"normalizer": "my_normalizer"
}
}
}
}'
# 写入三个文档
curl -X PUT "localhost:9200/index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"foo": "BAR"
}'
curl -X PUT "localhost:9200/index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"foo": "bar"
}'
curl -X PUT "localhost:9200/index/_doc/3?pretty" -H 'Content-Type: application/json' -d'
{
"foo": "baz"
}'
下面的查询会匹配文档 1 和 2,这是因为在索引和查询的时候都将 BAR 转换成了 bar:
curl -X GET "localhost:9200/index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"foo": "BAR"
}
}
}'
此外,由于转换操作先于索引操作,因此当执行聚合操作时返回的是转换后的值:
curl -X GET "localhost:9200/index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"size": 0,
"aggs": {
"foo_terms": {
"terms": { "field": "foo" }
}
}
}'
返回结果如下:
{
"took": 43,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped" : 0,
"failed": 0
},
"hits": {
"total" : {
"value": 3,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"foo_terms": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "bar",
"doc_count": 2
},
{
"key": "baz",
"doc_count": 1
}
]
}
}
}
2.3 boost
boost 字段用于设置字段的权重,比如,设置关键字出现在 title 字段的权重是出现在 content 字段中权重的两倍,其中 content 字段的默认权重是 1,具体 mapping 如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"title": {
"type": "text",
"boost": 2
},
"content": {
"type": "text"
}
}
}
}'
2.4 coerce
coerce 属性用于清除脏数据,默认值是 true。因为整型数字 5 有可能会被写成字符串 “5” 或浮点数 5.0。该属性可以用来清除脏数据,使得字符串和浮点数会被强制转换为整数类型。如下示例:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"number_one": {
"type": "integer"
},
"number_two": {
"type": "integer",
"coerce": false
}
}
}
}'
写入两条测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"number_one": "10"
}'
curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"number_two": "10"
}'
由于 Mapping 中指定了 number_one 字段是 integer 类型,虽然插入的数据类型是字符串,但依然可以插入成功,而 number_two 字段关闭了 coerce 属性,因此插入失败。
可以在索引级别设置 index.mapping.coerce 设置,以在所有映射类型中全局强制禁用:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.mapping.coerce": false
}
}'
2.5 copy_to
copy_to 参数可以将多个字段的值复制到一个组字段中(复制的是字段内容而不是 terms),然后将组字段作为单个字段进行查询。但 copy_to 的目标字段不会出现在 _source 中,只用于查询语句。
下面示例把 first_name 字段和 last_name 字段的内容复制到了 full_name 字段中:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}'
写入一篇测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"first_name": "John",
"last_name": "Smith"
}'
first_name 和 last_name 字段的值会被复制到 full_name 字段中。这样,我们既可以分别查询 first_name 和 last_name 字段,也可以查询 full_name 字段。
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"full_name": {
"query": "John Smith",
"operator": "and"
}
}
}
}'
2.6 doc_values
doc_values 参数是为了加快排序、聚合操作,在建立倒排索引的时候,额外增加一个列式存储映射,是一种空间换时间的做法。该参数默认开启,确定不需要聚合或排序的字段可以关闭 doc_values 以节省存储空间。
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}'
2.7 dynamic
2.8 enabled
Elasticseaech 默认会索引所有的字段,而有些字段只需要存储,没有查询或聚合的需求,这种情况下就可以使用 enabled 参数来控制。enabled 参数可以作用于 Mapping 的最顶层或 object 字段,关闭后 Elasticseaech 会完全跳过对字段内容的解析,该字段只能从 _source 中获取,但是不能以任何形式被搜索。
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"user_id": {
"type": "keyword"
},
"session_data": {
"type": "object",
"enabled": false
}
}
}
}'
# 全局禁用
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"enabled": false
}
}'
注意,因为 Elasticsearch 完全跳过了对字段内容的解析,所以可以将非对象数据添加到禁用的字段中。
2.9 fielddata
搜索要解决的问题是:包含查询关键词的文档有哪些?聚合恰恰相反,聚合要解决的问题是:文档包含了哪些词项?大多数字段在索引时都会生成 doc_values 但 text 字段除外。取而代之 text 字段在查询时会生成一个 fielddata 的数据结构,fielddata 在字段首次被聚合、排序或使用脚本时生成。Elasticsearch 通过读取磁盘上的倒排记录表重新生成文档词项关系,最后在 Java 堆内存中排序。
text 字段的 fielddata 属性默认是关闭的,因为开启 fielddata 非常消耗内存。在你开启 text 字段前,想清楚为什么要在 text 类型的字段上做聚合、排序操作,大多数情况下这么做是没有意义的。给 text 类型的字段开启 fielddata 的命令如下:
curl -X PUT "localhost:9200/my_index/_mapping?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"my_field": {
"type": "text",
"fielddata": true
}
}
}'
2.10 format
在 JSON 文档中,日期表示为字符串。Elasticsearch 使用一组预先配置的格式来识别这些字符串,并将其解析为 UTC 中表示 milliseconds-since-the-epoch 的长整型值。format 参数就是用于指定 date 类型的日期格式的。
下面的命令中给 date 字段设置了 3 种可接受的日期类型:2017-08-01、2017-08-01 12:10:10、1501560610 都是符合格式的日期值,更多的日期格式及其说明见官网:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd || yyyy-MM-dd HH:mm:ss || epoch_millis"
}
}
}
}'
2.11 ignore_above
ignore_above 用于指定字符串的最大长度,超过该长度的字段将不会被建立索引或存储。对于字符串数组,将分别对每个数组元素应用 ignore_above 属性,且比 ignore_above 长的字符串元素将不会被索引或存储。但文档的所有字段仍在 _source 元字段中存在。
使用示例如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"message": {
"type": "keyword",
"ignore_above": 20
}
}
}
}'
写入两个测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"message": "short document"
}'
curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"message": "another long document"
}'
由于 mapping 中指定了 ignore_above 字段的最大长度为 20,且第一个文档中的 message 字段的字符数小于 20,第二个超过 20,因此聚合结果中只会返回第一个文档(search 会返回两个):
curl -X GET "localhost:9200/_search?pretty" -H 'Content-Type: application/json' -d'
{
"aggs": {
"messages": {
"terms": {
"field": "message"
}
}
}
}'
返回结果如下:
{
"took": 56,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "my_index1",
"_type": "_doc",
"_id": "2",
"_score": 1.0,
"_source": {
"message": "another long document"
}
},
{
"_index": "my_index1",
"_type": "_doc",
"_id": "1",
"_score": 1.0,
"_source": {
"message": "short document"
}
}
]
},
"aggregations": {
"messages": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "short document",
"doc_count": 1
}
]
}
}
}
2.12 ignore_malformed
有时我们无法控制写入的数据,比如登录字段,有人可能填写的是日期类型,有人可能填写的是邮件地址。如果尝试将错误的数据类型索引到字段中,默认情况下会抛出一个异常,并拒绝整个文档。如果 ignore_malformed 参数设为 true 则允许忽略异常。类型不正确的字段不会被索引,但文档中的其他字段还是被正常处理。
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"number_one": {
"type": "integer",
"ignore_malformed": true
},
"number_two": {
"type": "integer"
}
}
}
}'
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"text": "Some text value",
"number_one": "foo"
}'
curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"text": "Some text value",
"number_two": "foo"
}'
上面的例子中 number_one 接受 integer 类型并将 ignore_malformed 设为 true,因此文档 1 能写入成功;而 number_two 的 ignore_malformed 属性为 false,因此写入失败。
可通过 index.mapping.ignore_malformed 属性进行全局控制:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"settings": {
"index.mapping.ignore_malformed": true
}
}'
2.13 index
index 属性指定字段是否索引,不索引也就不可搜索,默认为 true。如果设置为 false,则该字段不可被搜索。Elasticsearch 不会为该字段创建倒排索引,这样可以节省存储空间。
2.14 index_options
index_options 参数控制索引时存储哪些信息到倒排索引中,记录的内容越多,占用的存储空间就越大。具体取值见下表:
参数 | 作用 |
---|---|
docs | 只存储文档编号(默认值) |
freqs | 存储文档编号和词项频率,词项频率用来计算相关性得分 |
positions | 存储文档编号、词项频率、词项偏移位置,偏移位置可用于临近搜索和短语查询(text 字段类型默认) |
offsets | 存储文档编号、词项频率、词项偏移位置、词项开始和结束的字符位置都被存储,可以加速高亮显示 |
2.15 fields
fields 参数可以让同一字段有多种不同的索引方式。比如一个文本类型的字段,可以使用 text 类型做全文检索并使用 keyword 类型做聚合和排序,映射如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"city": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}'
写入两条测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"city": "New York"
}'
curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"city": "York"
}'
我们可以使用 city 字段进行全文搜索,使用 city.raw 字段进行排序和聚合:
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"city": "york"
}
},
"sort": {
"city.raw": "asc"
},
"aggs": {
"Cities": {
"terms": {
"field": "city.raw"
}
}
}
}'
2.16 norms
norms 参数用于标准化文档,以便查询时计算文档的相关性。norms 虽然对评分有用,但是会消耗较多的磁盘空间,如果不需要对某个字段进行评分最好关闭 norms,尤其是对于仅用于过滤或聚合的字段更是如此。
curl -X PUT "localhost:9200/my_index/_mapping?pretty" -H 'Content-Type: application/json' -d'
{
"properties": {
"title": {
"type": "text",
"norms": false
}
}
}'
2.17 null_value
如果某一字段插入的值为 NULL,则该字段不会被索引也不可以被搜索,null_value 参数可以让值为 null 的字段显示的可索引、可搜索,只有 keyword 字段类型支持该设置。使用示例如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword",
"null_value": "NULL"
}
}
}
}'
上面的设置表示当遇到空值时自动替换为 “NULL”(不修改 _source 元文档),尝试写入两个测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"status_code": null
}'
curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'
{
"status_code": []
}'
尝试搜索空值:
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"term": {
"status_code": "NULL"
}
}
}'
文档 1 可以被搜索到,因为其 status_code 的值为 null,文档 2 不可以被搜索到,因为其 status_code 的值为空数组,但不是 null。