ES之Mapping

什么是Mapping

Mapping类似数据库中的schema的定义,作用如下

  • 定义索引中的字段的名称
  • 定义字段的数据类型,例如字符串、数字、布尔......
  • 字段,倒排索引的相关配置,(Analyzed or Not Analyzed,Analyzer)

Mapping会把JSON文档映射成Lucene所需要的扁平格式

一个Mapping属于一个索引的Type

  • 每个文档都属于一个Type
  • 一个Type有一个Mapping定义
  • 7.0开始,不需要再Mapping定义中指定type信息

字段的数据类型

简单类型

  • Text / Keyword
  • Date
  • Interger / Floating
  • Boolean
  • IPv4 & IPv6

复杂类型 - 对象和嵌套对象

  • 对象类型 / 嵌套类型

特殊类型

  • geo_point & geo_shape / percolator

什么是Dynamic Mapping

  • 在写入文档的时候,如果索引不存在,会自动创建索引
  • Dynamic Mapping的机制,使得我们无需手动定义Mappings。ElasticSearch会自动根据文档信息,推算出字段的类型
  • 当时有时候会推算的不对,例如地理位置信息
  • 当类型如果设置的不对时,会导致一些功能无法正常使用,例如Range查询
类型推断错误:
#插入数据
PUT b_test/_doc/1
{
  "startLocation": {
    "lat": 32.004287,
    "lon": 118.779369
  }
}
#查看mapping
GET b_test/_mapping
{
  "b_test" : {
    "mappings" : {
      "properties" : {
        "startLocation" : {
          "properties" : {
            "lat" : {
              "type" : "float"
            },
            "lon" : {
              "type" : "float"
            }
          }
        }
      }
    }
  }
}
 
正确做法
#先创建索引
PUT b_test
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo_point"
            }
        }
    }
}
#再插入数据
PUT b_test/_doc/1
{
  "location": {
    "lat": 32.004287,
    "lon": 118.779369
  }
}

类型的自动识别

JSON类型

ElasticSearch 类型

JSON类型

ElasticSearch 类型

字符串
  • 匹配日期格式
  • 设置数字设置为float 或者long ,该选项默认关闭
  • 设置为Text,并且增加 keyword 子字段
布尔值boolean
浮点数float
整数long
对象Object
数组由第一个非空数值的类型所决定
空值忽略

添加一个文档
// 写入文档,查看Mapping
PUT mapping_test/_doc/1
{
"firstName": "Chan",
"lastName": "Jackie",
"loginDate": "2018-07-24T10:29:48.103Z"
}// 查看Mapping 文件
GET mapping_test/_mapping
{
  "mapping_test" : {
    "mappings" : {
      "properties" : {
        "firstName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "lastName" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "loginDate" : {
          "type" : "date"
        }
      }
    }
  }
}
** 我们看到这里。es 将 loginDate 字符串自动处理成了 date 类型的**
 
#删除index
DELETE mapping_test
 
# dynamic mapping 推断字段的类型
PUT mapping_test/_doc/1
{
  "uid":"123",
  "isVip": false,
  "isAdmin":"true",
  "age":19,
  "heigh":180
}
// 查看Mapping 文件
GET mapping_test/_mapping
{
  "mapping_test" : {
    "mappings" : {
      "properties" : {
        "age" : {
          "type" : "long"
        },
        "heigh" : {
          "type" : "long"
        },
        "isAdmin" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "isVip" : {
          "type" : "boolean"
        },
        "uid" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

能否更改Mapping的字段类型

两种情况:

1、新增加字段

  • Dynamic 设置为true 时, 一旦有新增字段的文档写入, Mapping 也同时被更新
  • Dynamic 设置为false 时,Mapping 不会被更新,新增字段的数据无法被索引,但是信息会出现在_source 中
  • Dynamic 设置成Strict, 文档写入失败

2、对已有字段,一旦已经有数据写入,就不再支持修改字段定义

  • Lucene 实现的倒排索引,一旦生成后,就不允许修改
  • 如果希望改变字段类型,必须Reindex API, 重建索引

原因:

1、因为如果修改了字段的数据类型,会导致已被索引的属性无法被搜索,

2、但是如果是增加新的字段,就不会有这样的影响。

控制Dynamic Mappings

true

false

strict

文档可索引x
字段可索引xx
Mapping被更新xx
  • 当 dynamic 被设置为 false 的时候,存在新增字段的数据写入,该数据可以被索引。但是新增字段被丢弃
  • 当设置为 Strict 模式的时候,数据写入直接报错
#写入的文档加入新的字段,默认Mapping支持dynamic
PUT mapping_test/_doc/1
{
  "dynamicTest":"test"
}
 
#查询
GET /mapping_test/_search
{
  "version": true,
  "query": {
    "match": {
      "dynamicTest": "test"
    }
  }
}
 
#修改为dynamic false
PUT mapping_test/_mapping
{
  "dynamic": false
}
 
#新增anotherField
PUT mapping_test/_doc/10
{
  "anotherField":"test"
}
 
#查看数据
GET b_test/_doc/10
 
#该字段无法被索引,应为dynamic为false
GET /mapping_test/_search
{
  "version": true,
  "query": {
    "match": {
      "anotherField": "test"
    }
  }
}
 
#修改为dynamic strict
PUT mapping_test/_mapping
{
  "dynamic": "strict"
}
 
#写入数据出错, HTTP CODE 400
PUT mapping_test/_doc/10
{
  "lastField":"test"
}

如何显示定义一个Mapping

PUT /your_index
{
    "mappings": {
        "properties": {
            // 字段
        }
    }
}
 
#e.g.
PUT /scheduler_driver_intention_info
{
    "mappings": {
        "properties": {
            "intentionId": {
                "type": "long"
            },
            "productId": {
                "type": "long"
            },
            "driverId": {
                "type": "long"
            },
            "cooperationType": {
                "type": "integer"
            },
            "startLocation": {
                "type": "geo_point"
            },
            "truckType": {
                "type": "long"
            },
            "truckLength": {
                "type": "double"
            },
            "startCityList": {
                "type": "nested",
                "properties": {
                    "districtId": {
                        "type": "integer"
                    },
                    "districtName": {
                        "type": "keyword"
                    },
                    "cityId": {
                        "type": "integer"
                    },
                    "cityName": {
                        "type": "keyword"
                    },
                    "generalizationFlag": {
                        "type": "boolean"
                    }
                }
            },
            "endCityList": {
                "type": "nested",
                "properties": {
                    "districtId": {
                        "type": "integer"
                    },
                    "districtName": {
                        "type": "keyword"
                    },
                    "cityId": {
                        "type": "integer"
                    },
                    "cityName": {
                        "type": "keyword"
                    },
                    "generalizationFlag": {
                        "type": "boolean"
                    }
                }
            },
            "startExcludeCityList": {
                "type": "nested",
                "properties": {
                    "districtId": {
                        "type": "integer"
                    },
                    "districtName": {
                        "type": "keyword"
                    }
                }
            },
            "endExcludeCityList": {
                "type": "nested",
                "properties": {
                    "districtId": {
                        "type": "integer"
                    },
                    "districtName": {
                        "type": "keyword"
                    }
                }
            },
            "createTime": {
                "type": "long"
            },
            "createTimeStr": {
                "type": "text",
                "fields": {
                    "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                    }
                }
            },
            "updateTime": {
                "type": "long"
            },
            "updateTimeStr": {
                "type": "text",
                "fields": {
                    "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                    }
                }
            },
            "valid": {
                "type": "boolean"
            }
        }
    }
}
 
#索引新增字段
PUT your_index/_mapping
{
  "properties": {
    "aaa": {
      "type": "text"
    }
  }
}

为了减少输入工作量,减少出错概率,可以依照以下步骤

  • 创建一个临时的index,写入一些样本数据
  • 通过访问Mapping API获取该临时文件的动态Mapping定义
  • 修改后,使用该配置创建你的索引
  • 删除临时索引

为了减少输入工作量,减少出错概率,可以依照以下步骤

  • 创建一个临时的index,写入一些样本数据
  • 通过访问Mapping API获取该临时文件的动态Mapping定义
  • 修改后,使用该配置创建你的索引
  • 删除临时索引

控制当前字段是否被索引

Index - 控制当前字段是否被索引,默认为true,如果设置成false,该字段不可被搜索

# 设置index为false
PUT b_test
{
  "mappings": {
    "properties": {
      "firstName":{
        "type": "text"
      },
      "lastName":{
        "type": "text"
      },
      "mobile":{
        "type": "text",
        "index": false
      }
    }
  }
}
 
PUT /b_test/_doc/1
{
  "firstName": "zhang",
  "lastName": "san",
  "mobile": "123"
}
 
#报错
GET /b_test/_search
{
  "query": {
    "match": {
      "mobile": "123"
    }
  }
}

Index Options

1、四种不同的级别的Index Options 设置,可以控制倒排索引记录的内容

  • docs - 记录 doc id
  • freqs - 记录 doc id 和 term frequencies
  • positions - 记录 doc id 和 term frequencies / term position
  • offsets - doc id / term frequencies / term position /character offects

2、Text 类型默认记录 positions,其他默认为 docs
3、记录内容越多,占用存储空间越大

# 显示的创建Mapping
PUT b_test
{
  "mappings": {
    "properties": {
      "name":{
        "type": "text"
      },
      "address":{
        "type": "text"
      },
      "phone_num":{
        "type": "text",
        "index": false
      },
      "bio":{
        "type": "text",
        "index_options": "offsets"
      }
    }
  }
}

null_value

  • 需要对null 值进行搜索
  • 只有 KeyWord类型支持设定 null_Value
# 显示的创建Mapping
PUT b_test
{
  "mappings": {
    "properties": {
      "firstName":{
        "type": "text"
      },
      "lastName":{
        "type": "text"
      },
      "mobile":{
        "type": "keyword",
        "null_value": "NULL"
      }
    }
  }
}
 
PUT /b_test/_doc/1
{
  "firstName": "zhang",
  "lastName": "san",
  "mobile": null
}
# 查询 注意NULL为大写
GET b_test/_search?q=mobile:NULL
GET b_test/_search
{
  "query": {
    "match": {
      "mobile": "NULL"
    }
  }
}

copy_to设置

  • _all 在 7 中被 copy_to 所替代
  • 满意一些特定的搜索要求
  • copy_to 将字段的数值拷贝到 目标字段, 实现类似 _all 的作用
  • copy_to 的目标字段不出现在 _source 中
# 显示的创建Mapping
PUT b_test
{
  "mappings": {
    "properties": {
      "firstName":{
        "type": "text",
        "copy_to": "fullName"
      },
      "lastName":{
        "type": "text",
        "copy_to": "fullName"
      }
    }
  }
}
 
PUT /b_test/_doc/1
{
  "firstName": "zhang",
  "lastName": "san"
}
# 查询
GET b_test/_search?q=fullName:(zhang san)

数组类型

ES中不提供专门的数组类型,但是任何字段,都可以包含多个相同类型的数值

PUT b_test/_doc/1
{
  "firstName": "Chan",
  "lastName": "zhangsan"
}
 
PUT b_test/_doc/2
{
  "firstName": "Chan",
  "lastName": ["zhangsan","lisi"]
}
 
# 查询
GET /b_test/_search
{
  "query": {
    "term": {
      "lastName.keyword": "zhangsan"
    }
  }
}

对象类型和嵌套对象(Nested Object)

关系数据库的范式设计

1NF - 消除非主属性对键的部分函数依赖

2NF - 消除非主属性对键的传递函数依赖

3NF - 消除主属性对键的传递函数依赖

BCNF - 主属性不依赖于主属性

  • 范式化设计(Normalization)的主要目标时”减少不必要的更新“
  • 副作用:一个完全范式化设计的数据库会经常面临”查询缓慢“的问题(数据库越范式化,就需要Jion越多的表)
  • 范式化节省了存储空间,但是存储空间却越来越便宜
  • 范式化简化了更新,但是数据”读“取操作可能更多

Denormalization

  • 反范式化设计
    • 数据“Flattening”,不使用关联关系,而是在文档中保存冗余的数据拷贝
  • 优点:无需处理Joins操作,数据读取性能好
    • Elasticsearch 通过压缩_source字段,减少磁盘空间的开销
  • 缺点:不适合在数据频繁修改的场景
    • 一条数据(用户名)的改动,可能会引起很多数据的更新

ES中处理关联关系

  • 关系型数据库,一般会考虑 Normalize 数据;在Elasticsearch,往往考虑 Denormalize 数据
    • Denormalize 的好处:读的速度变快、无需表连接、无需行锁
  • Elasticsearch 并不擅长处理关联关系,我们一般采用以下四种方法处理关联
    • 对象类型
    • 嵌套对象(Nested Object)
    • 父子关联关系(Parent 、Child)
    • 应用端关联

对象类型:

PUT /blog
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text"
      },
      "time": {
        "type": "date"
      },
      "user": {
        "properties": {
          "city": {
            "type": "text"
          },
          "userid": {
            "type": "long"
          },
          "username": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
 
# 插入一条 Blog 信息
PUT blog/_doc/1
{
  "content":"I like Elasticsearch",
  "time":"2021-10-19T00:00:00",
  "user":{
    "userid":1,
    "username":"Jack",
    "city":"Shanghai"
  }
}
 
# 查询 Blog 信息
POST blog/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "content": "Elasticsearch"
          }
        },
        {
          "match": {
            "user.username": "Jack"
          }
        }
      ]
    }
  }
}

对象数组类型:

# 电影的Mapping信息
PUT my_movies
{
      "mappings" : {
      "properties" : {
        "actors" : {
          "properties" : {
            "first_name" : {
              "type" : "keyword"
            },
            "last_name" : {
              "type" : "keyword"
            }
          }
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
}
 
# 写入一条电影信息
POST my_movies/_doc/1
{
  "title":"Speed",
  "actors":[
    {
      "first_name":"Keanu",
      "last_name":"Reeves"
    },
 
    {
      "first_name":"Dennis",
      "last_name":"Hopper"
    }
 
  ]
}
# 查询电影信息
POST my_movies/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"actors.first_name": "Keanu"}},
        {"match": {"actors.last_name": "Hopper"}}
      ]
    }
  }
}

问题:为什么会搜索到不需要的结果?

  • 储存时,内部对象的边界没有在考虑在内,JSON格式被处理成扁平键值对的结构
  • 当对多个字段进行查询时,导致了意外的搜索结果
  • 可以用 Nested Data Type 解决这个问题

Nested Data Type

  • Nested 数据类型:允许对象数组中的对象呗独立索引
  • 使用 Nested 和 Properties 关键词,将所有 actors 索引到对个分隔的文档
  • 在内部,Nested 文档会被保存在两个 Lucene 文档中,查询时做join处理

PUT my_movies
{
      "mappings" : {
      "properties" : {
        "actors" : {
          "type": "nested",
          "properties" : {
            "first_name" : {"type" : "keyword"},
            "last_name" : {"type" : "keyword"}
          }},
        "title" : {
          "type" : "text",
          "fields" : {"keyword":{"type":"keyword","ignore_above":256}}
        }
      }
    }
}
 
# 写入一条电影信息
POST my_movies/_doc/1
{
  "title":"Speed",
  "actors":[
    {
      "first_name":"Keanu",
      "last_name":"Reeves"
    },
 
    {
      "first_name":"Dennis",
      "last_name":"Hopper"
    }
 
  ]
}
# Nested 查询
POST my_movies/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"title": "Speed"}},
        {
          "nested": {
            "path": "actors",
            "query": {
              "bool": {
                "must": [
                  {"match": {
                    "actors.first_name": "Keanu"
                  }},
                  {"match": {
                    "actors.last_name": "Hopper"
                  }}
                ]
              }
            }
          }
        }
      ]
    }
  }
}

倒排索引

倒排索引,是适合用于进行搜索的

倒排索引的结构

(1)包含这个关键词的document list
(2)包含这个关键词的所有document的数量:IDF(inverse document frequency)
(3)这个关键词在每个document中出现的次数:TF(term frequency)
(4)这个关键词在这个document中的次序
(5)每个document的长度:length norm
(6)包含这个关键词的所有document的平均长度

word doc1 doc2

dog     *       *
hello    *
you      *

倒排索引不可变的好处

(1)不需要锁,提升并发能力,避免锁的问题
(2)数据不变,一直保存在os cache中,只要cache内存足够
(3)filter cache一直驻留在内存,因为数据不变
(4)可以压缩,节省cpu和io开销

倒排索引不可变的坏处:每次都要重新构建整个索引

注意事项

1、在ES5.x里,一定要注意数值类型是否需要做范围查询,看似数值,但其实只用于Term或者Terms这类精确匹配的,应该定义为keyword类型。典型的例子就是索引web日志时常见的HTTP Status code。

2、如果RangeQuery的结果集很大,并且还需要和其他结果集更小的查询条件做AND的,应该升级到ES5.4+,该版本在底层引入的indexOrDocValuesQuery,可以极大提升该场景下RangeQuery的查询速度。

3、reindex重建索引

一个field的设置是不能被修改的,如果要修改一个Field或者primary shard那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scoll就查询指定日期的一段数据,交给一个线程即可。

如果说旧索引的名字,是old_index,新索引的名字是new_index,终端java应用,已经在使用old_index在操作了,难道还要去停止java应用,修改使用的index为new_index,才重新启动java应用吗?这个过程中,就会导致java应用停机,可用性降低,所以给java应用一个别名,这个别名是指向旧索引的,java应用先用index alias来操作,此时实际指向的是旧的my_index

①PUT /{my_index}/_alias/{alias_index}

②新建一个index,调整其mapping

③使用scroll api将数据批量查询出来

GET /my_index/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort": ["_doc"],
"size": 1
}

④采用bulk api将scoll查出来的一批数据,批量写入新索引

POST /_bulk
{ "index": { "_index": "my_index_new", "_type": "my_type", "_id": "2" }}
{ "content": "xxx" }

⑤反复循环,查询一批又一批的数据出来,采取bulk api将每一批数据批量写入新索引

⑥将alias_index切换到my_index_new上去,java应用会直接通过index别名使用新的索引中的数据,java应用程序不需要停机,零提交,高可用

POST /_aliases
{
"actions": [
{ "remove": { "index": "my_index", "alias": "alias_index" }},
{ "add": { "index": "my_index_new", "alias": "alias_index" }}
]
}

相关文档

官网文档
Mapping | Elasticsearch Guide [7.5] | Elastic

一文搞懂 Elasticsearch 之 Mapping
一文搞懂 Elasticsearch 之 Mapping - 云+社区 - 腾讯云

理解 Percolator 数据类型及 Percolate 查询
Elasticsearch:理解 Percolator 数据类型及 Percolate 查询-阿里云开发者社区

使用 ignore_above 限制字符串长度
Elasticsearch 7 : 使用 ignore_above 限制字符串长度 - 乐天笔记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值