Elasticsearch的Mapping,定义了索引的结构,类似于关系型数据库的Schema。Elasticsearch的Setting主要是为了定义搜索的最关键组件,即:Analyzer,也就是分析器。
Dynamic Mapping及常用字段类型
Mapping的定义
Mapping类似于关系型数据库的Schema,主要包含以下内容:
- 定义索引中字段的名称
- 定义字段的数据类型,如:字符串、数字、boolean等
- 可对字段设置倒排索引的相关配置,如是否需要分词,使用什么分词器
从7.x开始,一个Mapping只属于一个索引的type
- 每个文档属于一个type
- 一个type有且仅有一个Mapping定义
- 从7.x开始,不需要在Mapping中指定type信息,默认type为
_doc
常用字段类型
在Elasticsearch中,字段数据类型有以下常用的类型:
- 简单类型
- Text / Keyword - 文本 / 关键字
- Date - 日期
- Integer / Float - 数字 / 浮点
- Boolean - 布尔值
- IPv4 / IPv6 - ip地址
- 复杂类型,包括对象和数组
- 对象
- 数组
- 特殊类型,如地理信息
- geo_point / ...
动态Mapping
动态Mapping,英文名为Dynamic Mapping。
- 在写入文档时,如果索引不存在,会自动创建索引
- 这种机制,使得我们无需手动定义mappings。Elasticsearch会自动根据文档信息,推算出字段的类型
- 有的时候,Elasticsearch可能会推算不对,如:地理位置信息
- 当类型推算得不对时,可能导致一些功能无法正常运行,如Range查询。
常用类型的自动识别规则
类型 | 规则 |
---|---|
字符串 | 匹配到日期格式,设置成Date。 字符串为数字时,当成字符串处理,但我们设置转换为数字。 其他情况,类型就是Text,并且会增加keyword的子字段 |
布尔值 | Boolean |
浮点数 | Float |
整数 | Long |
对象 | Object |
数组 | 由第一个非空数值的类型决定 |
空值 | 忽略 |
# 写入文档,查看 Mapping
PUT mapping_test/_doc/1
{
"firstName": "Chan", -- Text
"lastName": "Jackie", -- Text
"loginDate": "2018-07-24T10:29:48.103Z" -- Date
}
# Dynamic Mapping,推断字段的类型
PUT mapping_test/_doc/1
{
"uid": "123", -- Text
"isVip": false, -- Boolean
"isAdmin": "true", -- Text
"age": 19, -- Long
"heigh": 180 -- Long
}
# 查看 Dynamic Mapping
GET mapping_test/_mapping
字段类型是否可修改
- 新增加的字段
- dynamic设为true时,新增字段的文档写入时,Mapping同时被更新
- dynamic设为false时,Mapping不会被更新,新增字段的数据无法被索引,但是会出现在_source中
- dynamic设为strict,文档将写入失败
- 已存在的字段,一旦数据被写入,就不再支持修改字段定义
- Lucene本身的限制
- 如果希望更改字段类型,必须Reindex api,即:重建索引。在数据量多的时候,开销将非常大
# dynamic设置为false
PUT idx1
{
"mapping": {
"_doc": {
"dynamic": "false"
}
}
}
# 修改为dynamic为false
PUT idx1/_mapping
{
"dynamic": false
}
# 查看索引
GET idx1/_mapping
dynamic属性和索引字段可变性的规则,我们可以总结如下:
\ | true | false | strict |
---|---|---|---|
文档可索引 | yes | yes | no |
字段可索引 | yes | no | no |
Mapping被更新 | yes | no | no |
显式Mapping及常见参数
在本文的上一段落,我们的Mapping都是自动生成的。自动生成机制虽然方便,但是也可能导致一些问题。比如:生成的字段类型不正确,字段的附加属性不满足我们的需求,等等。这时,我们可以通过显式Mapping的方式来解决。
那么,我们如何进行显式Mapping的设置呢?
- 参考官网api,纯手写
- 为减少工作量,减少出错概率,可如下进行:
- 创建一个临时index,写入一些样本数据
- 通过访问Mapping API获取该临时文件的动态Mapping定义
- 修改后,再使用此配置创建自己的索引
- 删除临时索引
我们推荐使用第二种方式,效率高,且不容易出错。
常见参数 - index
index,可用于设置字段是否被索引,默认为true,false即为不可搜索。在下述例子中,mobile字段将不能被搜索到。
# 设置 index 为 false
DELETE users
PUT users
{
"mappings" : {
"properties" : {
"firstName" : {
"type" : "text"
},
"lastName" : {
"type" : "text"
},
"mobile" : {
"type" : "text",
"index": false
}
}
}
}
常见参数 - index_options
记录索引级别。Text类型默认为positions,其他类型默认为docs。我们需要记住一条准则。
记录的内容越多,占用的存储空间就越大。
索引级别有以下几种,更细节的内容可参考【官网索引级别】
- docs
- freqs
- positions
- offsets
常见参数 - null_value
需要对Null值实现搜索时使用。只有keyword类型才支持设定null_value
。
# 设定Null_value
DELETE users
PUT users
{
"mappings" : {
"properties" : {
"firstName" : {
"type" : "text"
},
"lastName" : {
"type" : "text"
},
"mobile" : {
"type" : "keyword",
"null_value": "NULL"
}
}
}
}
PUT users/_doc/1
{
"firstName":"Zhang",
"lastName": "Fubing",
"mobile": null
}
PUT users/_doc/2
{
"firstName":"Zhang",
"lastName": "Fubing2"
}
# 查看结果,有且仅有_id为2的记录
GET users/_search
{
"query": {
"match": {
"mobile":"NULL"
}
}
}
常见参数 - copy_to
这个属性用于将当前字段拷贝到指定字段。
-
_all
在7.x版本已经被copy_to
所代替 - 可用于满足特定场景
-
copy_to
将字段数值拷贝到目标字段,实现类似_all
的作用 -
copy_to
的目标字段不出现在_source中
# 设置 Copy to
DELETE users
PUT users
{
"mappings": {
"properties": {
"firstName":{
"type": "text",
"copy_to": "fullName"
},
"lastName":{
"type": "text",
"copy_to": "fullName"
}
}
}
}
PUT users/_doc/1
{
"firstName":"Zhang",
"lastName": "Fubing"
}
GET users/_search?q=fullName:(Zhang Fubing)
特殊的数组类型
Elasticsearch不提供专门的数组类型。但任何字段,都可以包含多个相同类型的数值。
# 数组类型
PUT users/_doc/1
{
"name":"onebird",
"interests":"reading"
}
PUT users/_doc/1
{
"name":"twobirds",
"interests":["reading","music"]
}
POST users/_search
{
"query": {
"match_all": {}
}
}
# interests字段还是text类型
GET users/_mapping
多字段类型及自定义Analyzer
多字段类型
所谓多字段类型,即:一个字段可以有多个子字段。这种特性带来了以下好处。
- 增加一个keyword子字段,可用于精确匹配
- 可对子字段设置不同的analyzer
- 不通语言的支持
- 可对中文拼音字段进行搜索
- 可对搜索和索引指定不同的Analyzer
精确值和全文本
精确值(Exact Values) vs 全文本(Full Text)
- 精确值,包括数字、日期、具体的字符串(如“192.168.0.1”)
- Elasticsearch中类型为keyword,索引时,不需要做特殊的分词处理
- 全文本,非结构化的文本数据
- Elasticsearch中类型为text,索引时,需要对其进行分词处理
如下结构的数据,我们可以大致判断出哪些是精确值,哪些是全文本。其中的200、info、debug都是精确值。而message的内容为全文本。
{
"code": 200,
"message": "this is a error item, you can change your apollo config !",
"content": {
"tags": [
"info",
"debug"
]
}
}
自定义分词器
自定义分词器,可通过组合不同的Character Filter
、Tokenizer
和Token Filter
来实现。
Character Filter
,常用的字符过滤器包括:
类型 | 作用 |
---|---|
html strip | 去除html |
mapping | 字符串替换 |
pattern replace | 正则匹配替换 |
Tokenizer
,用于将原始文本按照一定规则切分为词(Term或Token)。我们除了使用Elasticsearch自动的分词器外,还可以自己通过开发插件的方式来实现。常用的分词器包括:
- whitespace
- standard
- uax_url_email
- pattern
- keyword
- path
- hierarchy
Token Filter
,分词过滤器。主要用于对输出的单词,进行增删改。常用的分词过滤器包括:
类型 | 作用 |
---|---|
lowercase | 转换为小写 |
stop | 去掉the、a、an等单词 |
synonym | 转换为近义词 |
在下面的例子中,我们实现了一个自定义分词器。
# 编写自定义分析器
PUT index1
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"char_filter": ["emoticons"],
"tokenizer": "punctuation",
"filter": [
"lowercase",
"english_stop"
]
}
},
"tokenizer": {
"punctuation": {
"type":"pattern",
"pattern": "[ .,!?]"
}
},
"char_filter": {
"emoticons": {
"type": "mapping",
"mappings": [
":) => _happy_",
":( => _sad_"
]
}
},
"filter": {
"english_stop": {
"type": "stop",
"stopwords": "_english_"
}
}
}
}
}
# 查看自定义分析器的效果
POST index1/_analyze
{
"analyzer": "my_custom_analyzer",
"text": "I'm a :) person, and you?"
}
总结
通过这篇文章,我们了解了Mapping的作用及常用字段类型,也知道了动态Mapping和显式Mapping的区别。另外,我们还了解了Mapping的多字段特性,以及如何自定义一个Analyzer。