抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

ElasticSearch 字段映射

img

概述

什么是映射

在创建索引时,可以预先定义字段的类型(映射类型,也就是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
2
3
4
5
6
7
8
9
10
PUT mapping_demo/_mapping
{
"properties": {
"datetime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}

日期格式

所以在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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PUT mapping_demo/_mapping
{
"properties": {
"age_range": {
"type": "integer_range"
}
}
}

# 指定年龄范围,可以使用 gt、gte、lt、lte。
PUT mapping_demo/_doc/3
{
"real_name":"大方哥",
"age_range":{
"gt":20,
"lt":30
}
}

复合类型

数组类型

es 中没有专门的数组类型。

​ 默认情况下,任何字段都可以有一个或者多个值,需要注意的是,数组中的元素必须是同一种类型。

使用示例

常用的数组类型是:

  1. 字符数组: [ “one”, “two” ]
  2. 整数数组: productid:[ 1, 2 ]
  3. 对象(文档)数组: “user”:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }],ElasticSearch内部把对象数组展开为 {“user.name”: [“Mary”, “John”], “user.age”: [12,10]}
对象类型(object)

由于 JSON 本身具有层级关系,所以文档包含内部对象。内部对象中,还可以再包含内部对象。

使用案例
1
2
3
4
5
6
7
8
9
10
PUT test/my/1
{
"employee":{
"age":30,
"fullname":{
"first":"hadron",
"last":"cheng"
}
}
}
嵌套类型(nested)

nested类型是一种特殊的对象object数据类型specialised version of the object datatype,允许对象数组彼此独立地进行索引和查询。

使用示例

Lucene中没有内部对象的概念,所以 es 会将对象层次扁平化,将一个对象转为字段名和值构成的简单列表,例如添加以下文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT nested/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}

user会被动态映射为object类型的,其内部转换成类似下面的文档格式:

1
2
3
4
5
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}

扁平化处理后,用户名之间的关联关系没有,如果我们使用alicewhite查询,就会查询出不存在的用户

如果您需要索引对象数组并保持数组中每个对象的独立性,请使用nested数据类型,而不是object数据类型。

1
2
3
4
5
6
7
8
9
10
11
PUT nested
{
"mappings": {
"properties": {
"user": {
"type": "nested"
}
}
}
}

在内部,嵌套对象将数组中的每个对象索引为一个单独的隐藏文档,这意味着每个嵌套对象都可以通过查询独立于其他对象进行nested查询。

优缺点

使用nested类型的优缺点:

  • 优点:文档存储在一起,读取性能高。
  • 缺点:更新父或者子文档时需要更新更个文档

地理类型

geo_point
使用场景
  • 查找某一个范围内的地理位置
  • 通过地理位置或者相对中心点的距离来聚合文档
  • 把距离整合到文档的评分中
  • 通过距离对文档进行排序
定义类型
1
2
3
4
5
6
7
8
9
10
11
12
# 定义geo_point字段类型
PUT people
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}

使用案例

可以通过5种方式来指定一个地理位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# object类型指定经纬度
PUT people/_doc/1
{
"text": "Geo-point as an object",
"location": {
"lat": 34.27,
"lon": 108.94
}
}
# 字符串形式指定经纬度,中间用逗号,分隔
PUT people/_doc/2
{
"text": "Geo-point as a string",
"location": "34.27,108.94"
}
# 将经纬度转个Geohash后储存
PUT people/_doc/3
{
"text": "Geo-point as a geohash",
"location": "uzbrgzfxuzup"
}
# 使用数组指定经纬度,注意经度在前,纬度在后
PUT people/_doc/4
{
"text": "Geo-point as an array",
"location": [108.94, 34.27]
}
# 使用WKT原点指定经纬度,注意经度在前,纬度在后
PUT people/_doc/5
{
"text": "Geo-point as a WKT POINT primitive",
"location": "POINT (108.94 34.27)"
}
查询方式

通过经纬度来指定两左上角top_left和右下角bottom_rigth,来查询范围中的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET people/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 30,
"lon": 100
},
"bottom_right": {
"lat": 40,
"lon": 110
}
}
}
}
}
字段参数

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
2
3
4
5
6
7
8
9
10
11
# 定义geo_shape字段类型
PUT people
{
"mappings": {
"properties": {
"location": {
"type": "geo_shape"
}
}
}
}
添加数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# point类型
PUT people/_doc/1
{
"location":{
"type": "point",
"coordinates": [108.94, 34.27]
}
}
# linestring类型
PUT people/_doc/2
{
"location":{
"type": "linestring",
"coordinates": [[108.94, 34.27], [100, 33]]
}
}
# polygon类型
POST /people/_doc/3
{
"location": {
"type": "polygon",
"coordinates": [
[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]
]
}
}

特殊类型

es中的特殊类型有很多种,下面主要介绍两种最常用的

ip类型

ip类型的字段用于存储IPv4或者IPv6的地址

1
2
3
4
5
6
7
8
9
10
11
12
PUT test
{
"mappings": {
"my":{
"properties": {
"nodeIP":{
"type": "ip"
}
}
}
}
}
token_count

token_count用于统计字符串分词后的词项个数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 增加了length字段,用于统计词项个数
PUT blog
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"length": {
"type": "token_count",
"analyzer": "standard"
}
}
}
}
}
}
# 添加文档
PUT blog/_doc/1
{
"title": "zhang san"
}
# 使用token_count进行查询
GET blog/_search
{
"query": {
"term": {
"title.length": 2
}
}
}

映射的元信息

在创建映射的时候我们定义的字段内容并非映射中所有的字段,每个文档都存在一些和系统有关的元数据,这些数据被存储在映射的元字段中,根据元数据的作用不同可以分为下面几部分:

文档特征元信息

字段 说明
_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
2
3
4
5
6
7
8
9
10
11
12
# 使用默认分词器
PUT blog
# 添加英文内容
PUT blog/_doc/1
{
"content": "Things in life are aften unpredictable,but the wise advance every step with confidence."
}
# 使用term vectors查看词条向量
GET blog/_termvectors/1
{
"fields": ["content"]
}

我们发现英文默认按照单词空格进行分词的

image-20220805142651250

中文分词

下面我们使用默认分词器为中文分词

1
2
3
4
5
6
7
8
9
10
11
12
# 首先不定义分析器
PUT blog
# 添加文档
PUT blog/_doc/1
{
"title": "中国人民解放军东部战区陆军部队在台湾海峡实施了远程火力实弹射击训练,取得了预期效果"
}
# 使用term vectors查看词条向量
GET blog/_termvectors/1
{
"fields": ["title"]
}

通过_termvectors词条向量可以看到,如果不使用分析器,es默认分析器会一个一个将中文拆分。这样的分词结果没有任何意义,因为只能使用单个汉字,而不能使用词语。

image-20220804153014026

使用IK分词器

默认分词器不适合于中文分词,我们使用IK分词器试试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 删除索引
DELETE blog
# 添加索引并设置title为text类型,并设置IK分词器
PUT blog
{
"mappings": {
"properties": {
"title": {
"type": "content",
"analyzer": "ik_smart"
}
}
}
}

# 添加文档
PUT blog/_doc/1
{
"title": "中国人民解放军东部战区陆军部队在台湾海峡实施了远程火力实弹射击训练,取得了预期效果"
}
# 使用term vectors查看词条向量
GET blog/_termvectors/1
{
"fields": ["content"]
}

使用了正确的分词器,就可以将中文正确的分开了

image-20220805140921988

search_analyzer

通常情况下,索引和查询应该使用相同的分析器,以确保查询时的术语与倒排索引中的术语具有相同的格式

​ 但有时,在搜索时使用不同的分析器是有意义的,例如在使用edge_ngrapm tokenizer自动补全。

​ 默认情况下,查询时也是使用analyzer参数定义的分析器,但有些时候,为了查询更加精准,或是满足不同业务场景,可以通过search_analyzer参数来覆盖。

查询场景

我们看下下面的使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
PUT blog/_doc/1
{
"content": "普通高中生平均年龄15-18岁"
}

GET blog/_search
{
"query": {
"match": {
"content": "普通高中"
}
}
}

使用普通高中查询文档,此时查询默认也是使用analyzer定义的ik_smart分析器,查询失败!!

image-20220805143409828

查询失败原因

首先我们使用_termvectors查看词条向量,会发现ik_smart分析器将“普通高中生”分成“普通”和“高中生”两个词条

1
2
3
4
GET blog/_termvectors/1
{
"fields": ["content"]
}

image-20220805144355111

解析器分析

我们还可以使用ElasticSearch的解析器对文本进行分析,我们先来分析下ik_smart的分词效果

1
2
3
4
5
POST _analyze
{
"analyzer": "ik_smart",
"text": "普通高中生平均年龄15-18岁"
}

我们发现这个这个分词没有分出来普通高中生

image-20220805145121339

下面我们使用ik_max_word来进行分词

1
2
3
4
5
POST _analyze
{
"analyzer": "ik_max_word",
"text": "普通高中生平均年龄15-18岁"
}

我们发现这个分词效果符合我们的预期

image-20220805145248504

配置查询解析

对于“普通高中”,ik_smart分析器并不会分词,而是当做一个词条

​ 当使用“普通高中”去查询时,自然查询失败,此时就可以设置查询的分析器为ik_max_word,查询时将“普通高中”分成“普通”和“高中”两个词条,进行精确查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 删除索引
DELETE blog
# 添加索引并设置title为text类型,并设置IK分词器
PUT blog
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_max_word"
}
}
}
}

PUT blog/_doc/1
{
"content": "普通高中生平均年龄15-18岁"
}

GET blog/_search
{
"query": {
"match": {
"title": "普通高中"
}
}
}

我们发现这种已经有了命中数据了

image-20220805145533952

search_quote_analyzer

通过参数search_quote_analyzer设置短语指定分析器,这在处理禁用短语查询的停用词时特别有用

禁用停用词

要禁用短语的停用词,需要使用三个分析器设置字段:

  1. 通过参数analyzer指定索引分词,包含停用词参数。分词中包含停用词
  2. 通过参数search_analyze指定非短语查询分词器,分词中删除停用词
  3. 通过参数search_quote_analyzer指定短语查询分词器,分词中包含停用词
添加文档

下面我们添加以下文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 删除索引
DELETE blog
# 定义文档格式
PUT blog
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_max_word",
"search_quote_analyzer": "ik_smart"
}
}
}
}

PUT blog/_doc/1
{
"content": "歌唱美好地祖国"
}
PUT blog/_doc/2
{
"content": "歌唱美好的祖国"
}
非短语查询

下面我们使用非短语方式进行查询

1
2
3
4
5
6
7
8
GET blog/_search
{
"query": {
"match": {
"content": "歌唱美好的祖国"
}
}
}

使用非短语查询时,search_analyzer使用的ik_max_word会删除掉停止词“的”,导致两个文档都匹配,会查询出两条记录

image-20220805151831006

短语查询

下面我们使用query_string进行短语查询

1
2
3
4
5
6
7
8
GET blog/_search
{
"query": {
"query_string": {
"query": "\"歌唱美好的祖国\""
}
}
}

当使用query_string,并将查询短语用引号””引起来时,它被检测为短语查询,因此search_quote_analyzer启动,不会删除掉停用词“的”,只会查询出一条记录。

image-20220805152022747

normalizer

normalizer作用于keyword字段类型,用于解析前(索引或者查询)的标准化配置。

​ 归一化处理,主要针对 keyword 类型,在索引该字段或查询字段之前,可以先对原始数据进行一些简单的处理,然后再将处理后的结果当成一个词根存入倒排索 引中,默认为 null。

​ 我们知道,keyword字段在索引或者查询时都不会进行分词,如果在索引前没有做好数据清洗,导致大小写不一致,例如 ambyAmby,使用 amby 就无法查询到 Amby 的文档,实际它们应该是相同的

创建文档

此时,我们就可以使用normalizer在索引之前以及查询之前进行文档的标准化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 删除索引
DELETE blog
# 定义文档格式 自定义my_normalizer,并设置为author字段的normalizer参数值
PUT blog
{
"settings": {
"analysis": {
"normalizer":{
"my_normalizer":{
"type":"custom",
"filter":["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"author":{
"type": "keyword",
"normalizer":"my_normalizer"
}
}
}
}
# 分别添加amby和Amby文档

PUT blog/_doc/1
{
"author":"amby"
}
PUT blog/_doc/2
{
"author":"Amby"
}
文档查询

接下来不论是使用amby还是Amby,甚至是AMBY,都能查询出两个文档来

1
2
3
4
5
6
7
8
GET blog/_search
{
"query": {
"term": {
"author": "AMBY"
}
}
}

image-20220805153055421

查看author字段的聚合返回归一化值,Amby在索引前已经被转成amby

1
2
3
4
5
6
7
8
9
10
11
GET blog/_search
{
"size": 0,
"aggs": {
"foo_terms": {
"terms": {
"field": "author"
}
}
}
}

image-20220805153230732

coerce

coerce 参数用来清除脏数据,默认为 true。

​ 数据不总是我们想要的,由于在转换 JSON body 为真正 JSON 的时候,整型数 字 5 有可能会被写成字符串”5”或者浮点数 5.0,这个参数可以将数值不合法的部分去除。

例如一个整数,在 JSON 中,用户可能输入了错误的类型:

1
2
{"age": "99"} # 字符串类型
{"age": 99.0} # 浮点类型
使用场景

例如一个整数,在 JSON 中,用户可能输入了错误的类型:

1
2
{"age": "99"} # 字符串类型
{"age": 99.0} # 浮点类型

这些都不是正确的数字格式,但是经过 coerce 之后,都能正确的存储为整数类型

不进行过滤

默认情况下coerce为rue,不对插入文档进行过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 删除索引
DELETE blog
# 设置age为整数类型
PUT blog
{
"mappings": {
"properties": {
"age":{
"type": "integer"
}
}
}
}

# 无论是整数,还是字符串或浮点类型,都能正确的存储
POST blog/_doc
{
"age": 99
}
POST blog/_doc
{
"age": "99"
}
POST blog/_doc
{
"age": 99.0
}
过滤文档

可以修改 coerce 为 false ,修改之后,只有正确输入整数类型,才能存储,否则报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 删除索引
DELETE blog
# 设置age为整数类型
PUT blog
{
"mappings": {
"properties": {
"age":{
"type": "integer",
"coerce": false
}
}
}
}

# 只有整数才能够被存储
POST blog/_doc
{
"age": 99
}
POST blog/_doc
{
"age": "99"
}
POST blog/_doc
{
"age": 99.0
}

image-20220805155326783

copy_to

copy_to 参数允许您创建自定义的_all 字段,这个参数可以将多个字段的值,复制到同一个字段中。

创建文档

我们创建一个文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 删除索引
DELETE blog
# 该索引会将type和content内容复制到full_content中
PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "text",
"copy_to": "full_content"
},
"content":{
"type": "text",
"copy_to": "full_content"
},
"full_content":{
"type": "text"
}
}
}
}
# 添加文档
PUT blog/_doc/1
{
"title":"news ",
"content":"东部战区导弹全部精准命中目标"
}

查询文档

我们根据生成的full_content字段进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET blog/_search
{
"query": {
"term": {
"full_content": "news"
}
}
}


GET blog/_search
{
"query": {
"term": {
"full_content": "东"
}
}
}

我们发现上面两个搜索都能够搜索到字段

image-20220805163331945

doc_values 和 fielddata

es 中的搜索主要是用到倒排索引,doc_values 参数是为了加快排序、聚合操作而生的,当建立倒排索引的时候,会额外增加列式存储映射,以空间换时间,

doc_values 默认是开启的,如果确定某个字段不需要排序或者不需要聚合,那么可以关闭 doc_values,大部分的字段在索引时都会生成 doc_values,除了 text,text 字段在查询时会生成一个 fielddata 的数据结构,fieldata 在字段首次被聚合、排序的时候生成。

两者区别

下面简单对比一下两者的区别:

doc_values fieldata
默认开启 默认关闭
索引时创建 使用时动态创建
磁盘 内存
不占用内存 不占用磁盘
索引速度降低一点 文档很多时,动态创建慢,占内存

fieldata 参数使用场景很少,因为text字段一般都不用与排序和聚合,所以如果真的需要用到这个参数,一定要想清楚为什么,是否还有可替代方法。

排序效果

下面用doc_values 演示排序效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
PUT users

PUT users/_doc/1
{
"age":100
}

PUT users/_doc/2
{
"age":99
}

PUT users/_doc/3
{
"age":98
}

PUT users/_doc/4
{
"age":101
}

GET users/_search
{
"query": {
"match_all": {}
},
"sort":[
{
"age":{
"order": "desc"
}
}
]
}

下面就是排序后的效果

image-20220805163845982

enabled

是否建立索引,默认情况下为 true

​ es 默认会索引所有的字段,但有时候某些类型的字段,无需建立索引,只是用来存储数据即可,例如图片url地址,此时可以通过 enabled 字段来控制,设置为 false 之后,就不能再通过该字段进行搜索了

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 删除索引
DELETE blog
# 创建问题并设置title不建立索引
PUT blog
{
"mappings": {
"properties": {
"title":{
"enabled": false
}
}
}
}
# 创建文档
PUT blog/_doc/1
{
"title":"东部战区导弹全部精准命中目标"
}
文档搜索
1
2
3
4
5
6
7
8
GET blog/_search
{
"query": {
"term": {
"title": "东部战区导弹全部精准命中目标"
}
}
}

我们发现是无法进行索引查询的

image-20220805165035659

format

format用来对文档的日期类型进行识别以及格式化,如果格式不符合就会报错

​ 在 JSON 文档中,日期表示为字符串,Elasticsearch 使用一组预先配置的格式来识别和解析这些字符串,并将其解析为 long 类型的数值(毫秒),支持自定义格式,一次可以定义多个 format,多个日期格式之间,使用 || 符号连接,注意没有空格,如果没有指定format,日期类型默认的格式是:strict_date_optional_time||epoch_millis

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 删除索引
DELETE users
# 创建索引文件,类型是date指定格式化方式
PUT users
{
"mappings": {
"properties": {
"birthday":{
"type": "date",
"format": "yyyy-MM-dd||yyyy-MM-dd HH:mm:ss"
}
}
}
}
# 添加文档
PUT users/_doc/1
{
"birthday":"2020-11-11"
}

PUT users/_doc/2
{
"birthday":"2020-11-11 11:11:11"
}
# 如果日期格式不是定义的格式就会插入错误
PUT users/_doc/3
{
"birthday":"2020-11-25T00:00:00"
}

image-20220805165843606

ignore_above

在ElasticSearch中keyword类型字段可以设置ignore_above属性(默认是10) ,表示最大的字段值长度,超出这个长度的字段将不会被索引,但是会存储,这个字段只适用于 keyword 类型。

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 删除索引
DELETE blog
# 创建索引文件,设置title长度为10
PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "keyword",
"ignore_above": 10
}
}
}
}
# 添加文档
PUT blog/_doc/1
{
"title":"东部战区"
}

PUT blog/_doc/2
{
"title":"东部战区导弹全部精准命中目标"
}
查询文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 能够查询出来数据
GET blog/_search
{
"query": {
"term": {
"title": "东部战区"
}
}
}
# 查询不出来数据
GET blog/_search
{
"query": {
"term": {
"title": "东部战区导弹全部精准命中目标"
}
}
}
# 发现数据已经被存储了
GET blog/_doc/2

image-20220805171137642

ignore_malformed

ignore_malformed 参数可以忽略不规则的数据,该参数默认为 false

​ 对于age字段,有人可能填写的是Integer类型,也有人填写的是字符串格式,给一个字段索引不合适的数据类型发生异常,导致整个文档索引失败,如果ignore_malformed参数设为true,异常会被忽略,出异常的字段不会被索引,其它字段正常索引。

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 删除索引
DELETE users
# 创建索引文件
PUT users
{
"mappings": {
"properties": {
"age":{
"type": "integer",
"ignore_malformed": true
}
}
}
}

PUT users/_doc/1
{
"age":99
}

PUT users/_doc/2
{
"age":"abc"
}

PUT users/_doc/2
{
"age":"abc"
}
index

index 参数用于指定一个字段是否被索引,为 true 表示字段被索引,false 表示字段不被索引

注意,如果字段不能被索引,也就不能通过该字段进行搜索。

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 删除索引
DELETE users

PUT users
{
"mappings": {
"properties": {
"age":{
"type": "integer",
"index": false
}
}
}
}

PUT users/_doc/1
{
"age":99
}
查询
1
2
3
4
5
6
7
8
GET users/_search
{
"query": {
"term": {
"age": 99
}
}
}

image-20220805180713436

null_value

值为null的字段不索引也不可以被搜索,null_value 可以让值为 null的字段显式的可索引、可搜索

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 删除索引
DELETE users
# 创建索引设置name如果为null,设置值为null_value
PUT users
{
"mappings": {
"properties": {
"name":{
"type": "keyword",
"null_value": "null_value"
}
}
}
}

# 添加文档
PUT users/_doc/1
{
"name":null,
"age":99
}


搜索文档
1
2
3
4
5
6
7
8
9
GET users/_search
{
"query": {
"term": {
"name": "null_value"
}
}
}

image-20220805181406083

position_increment_gap

被解析的 text 字段会将 term 的位置考虑进去,目的是为了支持近似查询和短语查询

​ 我们去索引一个含有多个值的 text 字段时,会在各个值之间添加一个假想的空间,将值隔开,这样就可以有效避免一些无意义的短语匹配,间隙大小通过 position_increment_gap 参数来控制,默认是 100

默认方式进行查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 删除索引
DELETE users
# 创建索引
PUT users
# 添加一个数组
PUT users/_doc/1
{
"name":["zhang san","li si"]
}
# 当我们使用“zhang san”或“li si”,可以查询到。如果使用“san li”则查询不了,因为数组中两个值之间间隔了100的空隙,需要指定空隙大小进行查询。
GET users/_search
{
"query": {
"match_phrase": {
"name": {
"query": "san li"
}
}
}
}
# 查询时通过slop指定
GET users/_search
{
"query": {
"match_phrase": {
"name": {
"query": "san li",
"slop": 100
}
}
}
}
position_increment_gap查询
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 删除索引
DELETE users
# 创建索引
PUT users
{
"mappings": {
"properties": {
"name":{
"type": "text",
"position_increment_gap": 0
}
}
}
}
# 添加一个数组
PUT users/_doc/1
{
"name":["zhang san","li si"]
}
# 当我们使用“zhang san”或“li si”,可以查询到。如果使用“san li”则查询不了,因为数组中两个值之间间隔了100的空隙,需要指定空隙大小进行查询。
GET users/_search
{
"query": {
"match_phrase": {
"name": {
"query": "san li"
}
}
}
}

image-20220805182421486

properties

properties 是最常用的参数之一,可以作用于 mappingsobject 字段、 nested 字段中,进行属性的添加,这些属性可以是任意字段类型,包括 objectnested 。可以通过以下三种方式添加属性:

使用方式

可以通过以下三种方式添加属性:

  • 创建索引时定义。
  • 使用PUT mapping 添加或更新字段类型时定义。
  • 索引包含新字段的文档时,通过动态映射定义。
创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
PUT people
{
"mappings": {
"properties": {
"manager": {
"properties": {
"age": { "type": "integer" },
"name": { "type": "text" }
}
},
"employees": {
"type": "nested",
"properties": {
"age": { "type": "integer" },
"name": { "type": "text" }
}
}
}
}
}

PUT people/_doc/1
{
"manager": {
"name": "Alice White",
"age": 30
},
"employees": [
{
"name": "John Smith",
"age": 34
},
{
"name": "Peter Brown",
"age": 26
}
]
}
fields

fields 参数可以让同一字段有多种不同的索引方式,比如一个 String 类型的字段, 可以使用 text 类型做全文检索,使用 keyword 类型做聚合和排序

创建文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 删除索引
DELETE blog

PUT blog
{
"mappings": {
"properties": {
"title":{
"type": "text",
"fields": {
"raw":{
"type":"keyword"
}
}
}
}
}
}
# 添加数据
PUT blog/_doc/1
{
"title":"百度地图被搜崩了"
}





全文检索

title字段用于全文检索

1
2
3
4
5
6
7
8
GET blog/_search
{
"query": {
"match": {
"title": "百度"
}
}
}
关键字查询

title.raw用于关键字查询

1
2
3
4
5
6
7
8
9
GET blog/_search
{
"query": {
"term": {
"title.raw": "百度地图被搜崩了"
}
}
}

image-20220805184230858

更多字段

还有更多字段,可以在这里进行查询

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 信息、位置信息和偏移信息

评论