ElasticSearch 字段映射
概述
什么是映射
在创建索引时,可以预先定义字段的类型(映射类型,也就是type,一个索引可以有一个或多个类型)及相关属性
数据库建表的时候,我们DDL依据一般都会指定每个字段的存储类型,例如:varchar、int、datetime等,目的很明确,就是更精确的存储数据,防止数据类型格式混乱,在Elasticsearch中也是这样,创建爱你索引的时候一般也需要指定索引的字段类型,这种方式称为映射(Mapping)。
映射的作用
Elasticsearch会根据JSON源数据的基础类型猜测你想要的字段映射,将输入的数据转变成可搜索的索引项
Mapping就是我们定义的字段的数据类型,同时告诉Elasticsearch如何索引数据以及是否可以被搜索,会让索引建立的更加细致和完善
映射的创建
和传统数据库不同,传统的数据库我们尝试向表中插入数据的前提是这个表已经存在数据结构的定义,且插入数据的字段要在表结构中被定义,而ES的映射的创建支持主动和被动创建。
被动创建(动态映射)
此时字段和映射类型不需要事先定义,只需要存在文档的索引,当向此索引添加数据的时候当遇到不存在的映射字段,ES会根据数据内容自动添加映射字段定义。
动态映射规则
使用动态映射的时候,根据传递请求数据的不同会创建对应的数据类型
JSON中数据类型 | Elasticsearch 数据类型 |
---|---|
null | 不添加任何字段 |
true或者false | boolean类型 |
浮点数据 | float类型 |
integer数据 | long类型 |
object | object类型 |
array | 取决于数组中的第一个非空值的类型。 |
string | 如果此内容通过了日期格式检测,则会被认为是date数据类型。如果此值通过了数值类型检测则被认为是double或者long数据类型,带有关键字子字段会被认为一个text字段 |
主动创建(显示映射)
动态映射只能保证最基础的数据结构的映射,所以很多时候我们需要对字段除了数据结构定义更多的限制的时候,动态映射创建的内容很可能不符合我们的需求,所以可以使用
PUT {index}/mapping
来更新指定索引的映射内容。
映射限制
为索引中的列定义太多太大的字段会使得映射变得十分臃肿,从而导致内存错误和难以恢复的情况。
尤其在使用动态映射的时候,新的数据中假如携带不存在的字段数据时系统会自动添加新的字段,假如不做出限制会使映射字段变得非常多。目前ES支持设置的参数有下面几个
参数 | 说明 |
---|---|
index.mapping.total_fields.limit | 限制索引中字段的最大数量,默认值是1000。 |
index.mapping.depth.limit | 字段结构中定义的最大深度,基于根节点定义的字段深度为1,,如果有一个对象映射,则深度为2,依此类推。默认值是20。 |
index.mapping.nested_fields.limit | 索引中嵌套类型的最大数目,默认为50。 |
index.mapping.nested_objects.limit | 索引嵌套类型的单个文档内嵌套JSON对象的最大数量,默认为10000。 |
index.mapping.field_name_length.limit | 自定名称的最大长度,模型情况下是没有限制的。 |
映射数据类型
核心类型
字符串类型
string
string类型在ElasticSearch 旧版本中使用较多,从ElasticSearch 5.x开始不再支持string,由text和keyword类型替代
text
当一个字段是要被全文搜索的,比如Email内容、产品描述,应该使用text类型
设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项,text类型的字段不用于排序,很少用于聚合。
keyword
keyword类型适用于索引结构化的字段,比如email地址、主机名、状态码和标签
如果字段需要进行过滤(比如查找已发布博客中status属性为published的文章)、排序、聚合,keyword类型的字段只能通过精确值搜索到,常常被用来过滤、排序和聚合
数字类型
以下是整数类型的取值范围
类型 | 取值范围 |
---|---|
long | -263 ~ 263 |
integer | -231 ~ 231 |
short | -215 ~ 215 |
byte | -27 ~ 27 |
double | 64位的双精度 IEEE754 浮点类型 |
float | 32位的双精度 IEEE754 浮点类型 |
half_float | 16位的双精度 IEEE754 浮点类型 |
scaled_float | 缩放类型的浮点类型 |
注意事项
- 在满足需求的情况下,优先使用范围小的字段。字段长度越小,索引和搜索的效率越高。
- 浮点数,优先考虑使用scaled_float:例如保存一个价格为99.99的商品,当使用了scaled_float类型,定义比例因子scaling_factor为100,在底层保存的数据就是整数9999,可以提高效率。而在运算时,API还是以浮点类型进行运算。
日期类型
JSON中并没有日期类型
不管是什么样的格式,es 内部都会先将时间转为 UTC,然后将时间按照 millseconds-since-the-epoch
来存储成long类型,在查询的时候,再根据字段定义的格式转为字符串输出。
日期格式示例
日期类型默认的格式是:
strict_date_optional_time||epoch_millis
,可以通过format
属性来自定义日期格式
1 | PUT mapping_demo/_mapping |
日期格式
所以在es中日期类型可以表现成很多种:
- 日期格式字符串:“2020-12-28”或”2020/12/28 11:11:11”
- 毫秒级别的long类型
- 秒级别的integer类型
布尔类型
JSON 中的 “true”、“false”、true、false 都可以存储到布尔类型中。
二进制类型
二进制字段是指用base64来表示索引中存储的二进制数据
可用来存储二进制形式的数据,例如图像,默认情况下,该类型的字段只存储不索引,二进制类型只支持index_name属性。
范围类型
顾名思义,范围类型字段中存储的内容就是一段范围,例如年龄30-55岁,日期在2020-12-28到2021-01-01之间等
类型范围
es中有六种范围类型:
- integer_range
- float_range
- long_range
- double_range
- date_range
- ip_range
使用案例
1 | PUT mapping_demo/_mapping |
复合类型
数组类型
es 中没有专门的数组类型。
默认情况下,任何字段都可以有一个或者多个值,需要注意的是,数组中的元素必须是同一种类型。
使用示例
常用的数组类型是:
- 字符数组: [ “one”, “two” ]
- 整数数组: productid:[ 1, 2 ]
- 对象(文档)数组: “user”:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }],ElasticSearch内部把对象数组展开为 {“user.name”: [“Mary”, “John”], “user.age”: [12,10]}
对象类型(object)
由于 JSON 本身具有层级关系,所以文档包含内部对象。内部对象中,还可以再包含内部对象。
使用案例
1 | PUT test/my/1 |
嵌套类型(nested)
nested
类型是一种特殊的对象object数据类型specialised version of the object datatype
,允许对象数组彼此独立地进行索引和查询。
使用示例
Lucene中没有内部对象的概念,所以 es 会将对象层次扁平化,将一个对象转为字段名和值构成的简单列表,例如添加以下文档:
1 | PUT nested/_doc/1 |
user
会被动态映射为object
类型的,其内部转换成类似下面的文档格式:
1 | { |
扁平化处理后,用户名之间的关联关系没有,如果我们使用
alice
和white
查询,就会查询出不存在的用户
如果您需要索引对象数组并保持数组中每个对象的独立性,请使用nested
数据类型,而不是object
数据类型。
1 | PUT nested |
在内部,嵌套对象将数组中的每个对象索引为一个单独的隐藏文档,这意味着每个嵌套对象都可以通过查询独立于其他对象进行nested
查询。
优缺点
使用
nested
类型的优缺点:
- 优点:文档存储在一起,读取性能高。
- 缺点:更新父或者子文档时需要更新更个文档
地理类型
geo_point
使用场景
- 查找某一个范围内的地理位置
- 通过地理位置或者相对中心点的距离来聚合文档
- 把距离整合到文档的评分中
- 通过距离对文档进行排序
定义类型
1 | 定义geo_point字段类型 |
使用案例
可以通过5种方式来指定一个地理位置:
1 | object类型指定经纬度 |
查询方式
通过经纬度来指定两左上角top_left和右下角bottom_rigth,来查询范围中的位置:
1 | GET people/_search |
字段参数
geo_point
字段参数:
- ignore_malformed:默认为false,存储格式错误的地理位置将会抛出异常,并且文档也不会写入。true则忽略格式错误的地理位置。
- ignore_z_value:默认为true,接受并存储第三维z轴坐标,但仅索引经纬度。false则指接受二维经纬度,超过二维的地理坐标将会抛出异常。
- null_value:接受null值的地理位置,表示该字段被视为丢失。
geo_shape
GeoJson是什么
在学习
geo_shape
之前,我们先要了解一下GeoJson
。
GeoJSON是一种对各种地理数据结构进行编码的格式,基于Javascript对象表示法(JavaScript Object Notation, 简称JSON)的地理空间信息数据交换格式,GeoJSON对象可以表示几何、特征或者特征集合。GeoJSON支持下面几何类型:点、线、面、多点、多线、多面和几何集合。GeoJSON里的特征包含一个几何对象和其他属性,特征集合表示一系列特征
GeoJson | geo_shape | 描述 |
---|---|---|
Point | point | 单个由经纬度描述的地理坐标。注意:Elasticsearch仅使用WGS-84坐标 |
LineString | linestring | 一个任意的线条,由两个以上点组成 |
Polygon | polygon | 一个封闭的多边形,其第一个点和最后一个点必须匹配,由四个以上点组成 |
MultiPoint | multipoint | 一组不连续的点 |
MultiLineString | multilinestring | 多个不关联的线条 |
MultiPolygon | multipolygon | 多个多边形 |
GeometryCollection | geometrycollection | 与JSON形状相似的GeoJSON形状, 可以同时存在多种几何对象类型(例如,Point和LineString)。 |
N/A | envelope | 通过左上角和右下角两个点来指定一个矩形 |
N/A | circle | 由中心点和半径指定的圆 |
定义类型
1 | 定义geo_shape字段类型 |
添加数据
1 | point类型 |
特殊类型
es中的特殊类型有很多种,下面主要介绍两种最常用的
ip类型
ip类型的字段用于存储IPv4或者IPv6的地址
1 | PUT test |
token_count
token_count
用于统计字符串分词后的词项个数.
1 | 增加了length字段,用于统计词项个数 |
映射的元信息
在创建映射的时候我们定义的字段内容并非映射中所有的字段,每个文档都存在一些和系统有关的元数据,这些数据被存储在映射的元字段中,根据元数据的作用不同可以分为下面几部分:
文档特征元信息
字段 | 说明 |
---|---|
_index | 文档所属的索引 |
_type | 文档的映射类型。6.X之后已删除 |
_id | 文档的ID |
文档数据源元信息
需要注意的
_size
并不是默认就存在的,此字段需要mapper-size插件提供,该字段会以字节为单位索引_source
字段的大小。
字段 | 说明 |
---|---|
_source | 表示文档主体的原始JSON |
_size | _source字段的大小(以字节为单位) |
索引原信息
字段 | 说明 |
---|---|
_field_names | 用来索引文档中包含除了null之外的任何值得字段的名称 |
_ignored | 该字段索引并存储文档中因为格式错误而被忽略的字段的名称 |
路由原信息
字段 | 说明 |
---|---|
_routing | 将文档路由到特定碎片的自定义路由值 |
自定义元信息
字段 | 说明 |
---|---|
_meta | 此字段可以定义一些自定的信息,这些信息Elasticsearch不会使用他们,但是用户可以用其来保存特定的元数据,比如版本信息等。 |
映射参数
除了
type
参数,Mapping中还有另外27种映射参数。
有些参数对于所有字段类型都是通用的,例如boost
权重参数,而有些参数则只适用于特定字段,例如analyzer
参数只能作用于text
字段。
analyzer
将字符串转换成多个分词
例如,字符串 “The quick Brown Foxes.”,根据指定分词器可以得到分词:quick, brown, fox。分词器可以自定义,这使得可以有效地搜索大块文本中的单个单词。
此分析过程不仅需要在索引时进行,还需要在查询时进行:查询字符串需要指定相同或类似的分析器,以便在查询字符串分析得到的分词与索引中的分词格式相同。
Elasticsearch附带了需要预定义的分析器,无需进一步配置即可使用,它还附带了需要字符过滤器、分词器和分词过滤器,可以组合起来为每个索引配置自定义分析器。
analyzer
参数定义text
字段索引和查询所使用的分析器,默认使用es自带的Standard
标准分析器。
查询分析时,除非使用search_analyzer
映射参数覆盖,否则也是使用的也是analyzer
定义的分析器。
英文分词
我们先创建一个索引,不配置任何类型
1 | 使用默认分词器 |
我们发现英文默认按照单词空格进行分词的
中文分词
下面我们使用默认分词器为中文分词
1 | 首先不定义分析器 |
通过
_termvectors
词条向量可以看到,如果不使用分析器,es默认分析器会一个一个将中文拆分。这样的分词结果没有任何意义,因为只能使用单个汉字,而不能使用词语。
使用IK分词器
默认分词器不适合于中文分词,我们使用IK分词器试试
1 | 删除索引 |
使用了正确的分词器,就可以将中文正确的分开了
search_analyzer
通常情况下,索引和查询应该使用相同的分析器,以确保查询时的术语与倒排索引中的术语具有相同的格式
但有时,在搜索时使用不同的分析器是有意义的,例如在使用edge_ngrapm tokenizer自动补全。
默认情况下,查询时也是使用analyzer
参数定义的分析器,但有些时候,为了查询更加精准,或是满足不同业务场景,可以通过search_analyzer
参数来覆盖。
查询场景
我们看下下面的使用场景
1 | PUT blog/_doc/1 |
使用
普通高中
查询文档,此时查询默认也是使用analyzer
定义的ik_smart
分析器,查询失败!!
查询失败原因
首先我们使用
_termvectors
查看词条向量,会发现ik_smart分析器将“普通高中生”分成“普通”和“高中生”两个词条
1 | GET blog/_termvectors/1 |
解析器分析
我们还可以使用ElasticSearch的解析器对文本进行分析,我们先来分析下
ik_smart
的分词效果
1 | POST _analyze |
我们发现这个这个分词没有分出来
普通高中生
下面我们使用
ik_max_word
来进行分词
1 | POST _analyze |
我们发现这个分词效果符合我们的预期
配置查询解析
对于“普通高中”,ik_smart分析器并不会分词,而是当做一个词条
当使用“普通高中”去查询时,自然查询失败,此时就可以设置查询的分析器为ik_max_word,查询时将“普通高中”分成“普通”和“高中”两个词条,进行精确查询
1 | 删除索引 |
我们发现这种已经有了命中数据了
search_quote_analyzer
通过参数search_quote_analyzer设置短语指定分析器,这在处理禁用短语查询的停用词时特别有用
禁用停用词
要禁用短语的停用词,需要使用三个分析器设置字段:
- 通过参数analyzer指定索引分词,包含停用词参数。分词中包含停用词
- 通过参数search_analyze指定非短语查询分词器,分词中删除停用词
- 通过参数search_quote_analyzer指定短语查询分词器,分词中包含停用词
添加文档
下面我们添加以下文档
1 | 删除索引 |
非短语查询
下面我们使用非短语方式进行查询
1 | GET blog/_search |
使用非短语查询时,
search_analyzer
使用的ik_max_word会删除掉停止词“的”,导致两个文档都匹配,会查询出两条记录
短语查询
下面我们使用
query_string
进行短语查询
1 | GET blog/_search |
当使用
query_string
,并将查询短语用引号””引起来时,它被检测为短语查询,因此search_quote_analyzer
启动,不会删除掉停用词“的”,只会查询出一条记录。
normalizer
normalizer
作用于keyword
字段类型,用于解析前(索引或者查询)的标准化配置。
归一化处理,主要针对 keyword 类型,在索引该字段或查询字段之前,可以先对原始数据进行一些简单的处理,然后再将处理后的结果当成一个词根存入倒排索 引中,默认为 null。
我们知道,keyword
字段在索引或者查询时都不会进行分词,如果在索引前没有做好数据清洗,导致大小写不一致,例如 amby 和 Amby,使用 amby 就无法查询到 Amby 的文档,实际它们应该是相同的
创建文档
此时,我们就可以使用
normalizer
在索引之前以及查询之前进行文档的标准化。
1 | 删除索引 |
文档查询
接下来不论是使用amby还是Amby,甚至是AMBY,都能查询出两个文档来
1 | GET blog/_search |
查看author字段的聚合返回归一化值,Amby在索引前已经被转成amby
1 | GET blog/_search |
coerce
coerce
参数用来清除脏数据,默认为 true。
数据不总是我们想要的,由于在转换 JSON body 为真正 JSON 的时候,整型数 字 5 有可能会被写成字符串”5”或者浮点数 5.0,这个参数可以将数值不合法的部分去除。
例如一个整数,在 JSON 中,用户可能输入了错误的类型:
1 | {"age": "99"} # 字符串类型 |
使用场景
例如一个整数,在 JSON 中,用户可能输入了错误的类型:
1 | {"age": "99"} # 字符串类型 |
这些都不是正确的数字格式,但是经过
coerce
之后,都能正确的存储为整数类型
不进行过滤
默认情况下
coerce
为rue,不对插入文档进行过滤
1 | 删除索引 |
过滤文档
可以修改
coerce
为 false ,修改之后,只有正确输入整数类型,才能存储,否则报错
1 | 删除索引 |
copy_to
copy_to 参数允许您创建自定义的
_all
字段,这个参数可以将多个字段的值,复制到同一个字段中。
创建文档
我们创建一个文档
1 | 删除索引 |
查询文档
我们根据生成的
full_content
字段进行查询
1 | GET blog/_search |
我们发现上面两个搜索都能够搜索到字段
doc_values 和 fielddata
es 中的搜索主要是用到倒排索引,
doc_values
参数是为了加快排序、聚合操作而生的,当建立倒排索引的时候,会额外增加列式存储映射,以空间换时间,
doc_values
默认是开启的,如果确定某个字段不需要排序或者不需要聚合,那么可以关闭 doc_values,大部分的字段在索引时都会生成 doc_values
,除了 text,text 字段在查询时会生成一个 fielddata
的数据结构,fieldata
在字段首次被聚合、排序的时候生成。
两者区别
下面简单对比一下两者的区别:
doc_values | fieldata |
---|---|
默认开启 | 默认关闭 |
索引时创建 | 使用时动态创建 |
磁盘 | 内存 |
不占用内存 | 不占用磁盘 |
索引速度降低一点 | 文档很多时,动态创建慢,占内存 |
fieldata
参数使用场景很少,因为text字段一般都不用与排序和聚合,所以如果真的需要用到这个参数,一定要想清楚为什么,是否还有可替代方法。
排序效果
下面用
doc_values
演示排序效果:
1 | PUT users |
下面就是排序后的效果
enabled
是否建立索引,默认情况下为 true
es 默认会索引所有的字段,但有时候某些类型的字段,无需建立索引,只是用来存储数据即可,例如图片url地址,此时可以通过 enabled
字段来控制,设置为 false 之后,就不能再通过该字段进行搜索了
创建文档
1 | 删除索引 |
文档搜索
1 | GET blog/_search |
我们发现是无法进行索引查询的
format
format用来对文档的日期类型进行识别以及格式化,如果格式不符合就会报错
在 JSON 文档中,日期表示为字符串,Elasticsearch 使用一组预先配置的格式来识别和解析这些字符串,并将其解析为 long 类型的数值(毫秒),支持自定义格式,一次可以定义多个 format
,多个日期格式之间,使用 || 符号连接,注意没有空格,如果没有指定format
,日期类型默认的格式是:strict_date_optional_time||epoch_millis
创建文档
1 | 删除索引 |
ignore_above
在ElasticSearch中keyword类型字段可以设置ignore_above属性(默认是10) ,表示最大的字段值长度,超出这个长度的字段将不会被索引,但是会存储,这个字段只适用于 keyword 类型。
创建文档
1 | 删除索引 |
查询文档
1 | 能够查询出来数据 |
ignore_malformed
ignore_malformed
参数可以忽略不规则的数据,该参数默认为 false
对于age字段,有人可能填写的是Integer类型,也有人填写的是字符串格式,给一个字段索引不合适的数据类型发生异常,导致整个文档索引失败,如果ignore_malformed参数设为true,异常会被忽略,出异常的字段不会被索引,其它字段正常索引。
创建文档
1 | 删除索引 |
index
index
参数用于指定一个字段是否被索引,为 true 表示字段被索引,false 表示字段不被索引
注意,如果字段不能被索引,也就不能通过该字段进行搜索。
创建文档
1 | 删除索引 |
查询
1 | GET users/_search |
null_value
值为
null
的字段不索引也不可以被搜索,null_value
可以让值为null
的字段显式的可索引、可搜索
创建文档
1 | 删除索引 |
搜索文档
1 | GET users/_search |
position_increment_gap
被解析的 text 字段会将 term 的位置考虑进去,目的是为了支持近似查询和短语查询
我们去索引一个含有多个值的 text 字段时,会在各个值之间添加一个假想的空间,将值隔开,这样就可以有效避免一些无意义的短语匹配,间隙大小通过 position_increment_gap
参数来控制,默认是 100
默认方式进行查询
1 | 删除索引 |
position_increment_gap查询
1 | 删除索引 |
properties
properties
是最常用的参数之一,可以作用于mappings
、object
字段、nested
字段中,进行属性的添加,这些属性可以是任意字段类型,包括object
和nested
。可以通过以下三种方式添加属性:
使用方式
可以通过以下三种方式添加属性:
- 创建索引时定义。
- 使用PUT mapping 添加或更新字段类型时定义。
- 索引包含新字段的文档时,通过动态映射定义。
创建文档
1 | PUT people |
fields
fields
参数可以让同一字段有多种不同的索引方式,比如一个 String 类型的字段, 可以使用 text 类型做全文检索,使用 keyword 类型做聚合和排序
创建文档
1 | 删除索引 |
全文检索
title字段用于全文检索
1 | GET blog/_search |
关键字查询
title.raw用于关键字查询
1 | GET blog/_search |
更多字段
还有更多字段,可以在这里进行查询
index_options
index_options
参数用于控制索引时哪些信息被存储到倒排索引中,仅作用在 text 字段,有四种取值:
- docs:只存储文档编号。
- freqs:在docs基础上,存储词项频率。
- positions (默认):在freqs基础上,存储词项偏移位置。
- offsets:在positions基础上,存储词项开始和结束的字符位置。
norms
norms
对字段评分有用,text 类型会默认开启,如果不是特别需要,不要开启norms
。
similarity
similarity
参数用于指定文档的评分模型,使用到的情况不多,默认有三种:
- BM25:es 和 Lucene 默认的评分模型。
- classic:TF/IDF 评分。
- boolean:boolean 评分模型。
store
默认情况下,字段会被索引,也可以搜索,但是不会存储,虽然不会被存储的,但是 _source 中有一个字段的备份。如果想将字段存储下来,可以通过配置 store
参数来实现。
term_vectors
term_vectors
是通过分词器产生的词项信息,包括:
- 一组 terms
- 每个 term 的位置
- term 的首字符 / 尾字符与原始字符串原点的偏移量
term_vectors取值 | |
---|---|
no | 不存储信息 |
yes | 存储 term 信息 |
with_positions | 存储 term 信息和位置信息 |
with_offset | 存储 term 信息和偏移信息 |
with_positions_offset | 存储 term 信息、位置信息和偏移信息 |