一、引言
为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。
索引中每个文档都有 类型 。每种类型都有它自己的 映射 ,或者 模式定义 。映射定义了类型中的域,每个域的数据类型,以及Elasticsearch如何处理这些域。映射也用于配置与类型有关的元数据。
二、相关概念
Elasticsearch 支持如下简单域类型:
字符串: string(5.X以上版本没有string类型了,换成了text和keyword作为字符串类型。)- 字符串:
text
,keyword
;text
用于全文搜索keyword
用于关键词搜索。
- 整数 :
byte
,short
,integer
,long
- 浮点数:
float
,double
- 布尔型:
boolean
- 日期:
date
2.1、Text vs keyword
随着ElasticSearch 5.0的到来, 同时也迎来了该版本的重大特性之一: 移除了string类型。
这个变动的根本原因是string类型会给我们带来很多困惑: 因为ElasticSearch对字符串拥有两种完全不同的搜索方式。你可以按照整个文本进行匹配, 即关键词搜索(keyword search), 也可以按单个字符匹配, 即全文搜索(full-text search)。 对ElasticSearch稍有了解的人都知道, 前者的字符串被称为not-analyzed字符,而后者被称作analyzed字符串。
事实上, 同一种类型用于应对两种不同的使用场景是会让人崩溃的, 因为有些选项只对其一的场景设置有效.例如position_increment_gap
对not-analyzed
字符就不会起作用, 而像ignore_above
对于analyzed
字符串就很难区分它到底是对整个字符串的值有效还是对单独的每个分词有效(在这种场景, ignore_above
确实只对整个字符串值有效, 而对单个分词的限制可以使用limit
设置).
为了避免上述尴尬, string字段被拆分成两种新的数据类型: text用于全文搜索的,而keyword用于关键词搜索。
新的默认类型
做了这个类型分解之后, 我们对string字段的默认dynamic mappings
也做了改变。在以前刚接接触ElasticSearch时, 如果需要对某个字段的所有取值做聚合, 你不得不对这些数据重做索引. 假如你正在处理的文档中包含一个city
字段. 对这个字段做聚合的话会分别给出new
和york
的总数, 而非我们通常期望的New York
的总数.让人沮丧的是为了达到我们希望的结果, 我们必须对这个字段重新进行索引.
为了不让事情变得这么糟糕, ElasticSearch决定从Logstash中借取思路: 字符串将默认被同时映射成text
和keyword
类型. 例如对下面的文档进行索引后:
{
"foo": "bar"
}
ElasticSearch将会为你创建下面的动态映射(dynamic mappings
):
{
"foo": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
当然, 基于这个映射你即可以在foo
字段上进行全文搜索, 也可以通过foo.keyword
字段实现关键词搜索及数据聚合。
注:
字符串长度超过 ignore_above
设置的不会被索引和存储.
禁用这个特性也很方便:
你只需要在定义mapping时显式声明字符串字段的类型或者使用一个动态模板(dynamic template
)来匹配你所有的字符串字段即可. 例如通过下面的dynamic template
就可以恢复到在ElasticSearch 2.x中使用的dynamic template
的效果:
{
"match_mapping_type": "string",
"mapping": {
"type": "text"
}
}
如何迁移到新版本
通常, 迁移工作是非常容易的. 以前映射到analyzed的字符串的字段:
{
"foo": {
"type": "string",
"index": "analyzed"
}
}
如今只要映射为text
即可:
{
"foo": {
"type": "text",
"index": true
}
}
以前被定义为not_analyzed的字符串字段:
{
"foo": {
"type": "string",
"index": "not_analyzed"
}
}
也只需要被定义为keyword即可:
{
"foo": {
"type": "keyword",
"index": true
}
}
如上所述, string
字段被重新定义为text
和keyword
字段. 对于上面的index属性, 因为在新的定义中我们不需要三种状态(在以前的string
定义中可以是analyzed
, not_analyzed
和no
), 所以只简单的定义成了boolean
值, 以告知ElasticSearch是否可在该字段上进行搜索。
2.2、 动态映射
字段和映射类型在使用之前不需要定义。由于有了动态映射,只需对文档建立索引,就可以自动添加新的字段名。可以将新字段添加到顶级映射类型,也可以添加到内部对象和嵌套字段。
实际业务中,如果不明确设定字段类型,Elasticsearch有动态映射机制,会根据插入数据自动匹配对应的类型。
2.3、明确映射
您对数据的了解比Elasticsearch所能猜测的要多,因此,虽然动态映射在开始时可能很有用,但在某些情况下,您可能希望指定自己的显式映射。
您可以在创建索引并将字段添加到现有索引时创建字段映射。
三、设置映射
定制dynamic
策略:
true
:遇到陌生字段,就进行dynamic mappingfalse
:遇到陌生字段,就忽略strict
:遇到陌生字段,就报错
小结:
- 动态映射(dynamic:true):动态添加新的字段(或缺省)。
- 静态映射(dynamic:false):忽略新的字段。在原有的映射基础上,当有新的字段时,不会主动的添加新的映射关系,只作为查询结果出现在查询中。
- 严格模式(dynamic: strict):如果遇到新的字段,就抛出异常。
PUT /index0
{
"mappings":{
"dynamic":"strict",
"properties": {
"title":{"type": "text"},
"address":{
"type": "object",
"dynamic":"true"
}
}
}
}
PUT /my-index
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"name": { "type": "text" }
}
}
}
四、给已存在的映射添加一个字段
下面的示例添加了employee-id,这是一个关键字字段,其索引映射参数值为false。这意味着存储了雇员id字段的值,但是没有索引,也不能用于搜索。
PUT /my-index/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false
}
}
}
五、更新映射
除了受支持的映射参数外,您不能更改现有字段的映射或字段类型。更改现有字段可能会使已建立索引的数据无效。
如果需要更改字段的映射,请使用正确的映射创建新索引,并将数据重新索引到该索引中。
重命名字段将使已经在旧字段名称下建立索引的数据无效。相反,添加一个别名字段来创建一个替代字段名。
六、查看映射
通过 /_mapping
,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射。
GET /my-index/_mapping
{
"my-index" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "integer"
},
"email" : {
"type" : "keyword"
},
"employee-id" : {
"type" : "keyword",
"index" : false
},
"name" : {
"type" : "text"
}
}
}
}
}
在未指定映射的情况下就插入数据,查询映射可能出现如下的结果:
{
"customer" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
- ignore_above:
ignore_above 默认值是256,该参数的意思是,当字段文本的长度大于指定值时,不做倒排索引。
也就是说,当字段文本的长度大于指定值时,聚合、全文搜索都查不到这条数据。
ignore_above 最大值是32766,但是要根据场景来设置,比如说中文最大值应该是设定在10922。
ignore_above 背后实际的含义是,Lucene对一个文本的解析长度,当这个长度大于32766时,将不会落实analyze行为。
Elasticsearch中采用的是字符个数来定义igmore_above值, 而
lucene是采用byte字节;那么每个象形文字在utf-8中占位是3,
每个Literal字符在utf-8中占位是2, 每个ASCII字符在utf-8中占位是1.
象形文字字符(中文、韩文、日文): 10922 个字符(算法是: 32766 / 3).
Literal字符(印度问、俄文): 16383 个字符(算法是: 32766 / 2).
ASCII字符(a-zA-Z0-9以及~!@#$等特殊字符): 32766个字符(算法是: 32766).
七、查看特定字段的映射
GET /my-index/_mapping/field/employee-id
{
"my-index" : {
"mappings" : {
"employee-id" : {
"full_name" : "employee-id",
"mapping" : {
"employee-id" : {
"type" : "keyword",
"index" : false
}
}
}
}
}
}
八、测试映射
使用 analyze
API 测试字符串域的映射。比较下面两个请求的输出:
keywork类型
GET /my-index/_analyze
{
"field": "email",
"text": "Black-cats"
}
响应:
{
"tokens" : [
{
"token" : "Black-cats",
"start_offset" : 0,
"end_offset" : 10,
"type" : "word",
"position" : 0
}
]
}
text类型
GET /my-index/_analyze
{
"field": "name",
"text": "Black-cats"
}
响应:
{
"tokens" : [
{
"token" : "black",
"start_offset" : 0,
"end_offset" : 5,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "cats",
"start_offset" : 6,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}