映射是Elasticsearch中一个非常重要的概念,因为它定义了搜索的方式
引擎应处理文档及其字段。
搜索引擎执行以下两个主要操作:
这两个部分是严格连接的; 索引步骤中的错误导致不必要或丢失的搜索结果。
Elasticsearch在索引级别具有显式映射。 索引时,如果映射是未提供,则创建一个默认值,并从数据字段中猜测结构文件组成的。 然后,此新映射将自动传播到所有群集节点。
默认类型映射具有合理的默认值,但是当您要更改时,他们的行为或自定义索引的其他几个方面(存储,忽略,完成等),您需要提供一个新的映射定义。
我们将研究记录映射的所有可能的映射字段类型组成。
本章将介绍以下:
如果我们将索引视为SQL世界中的数据库,则映射类似于表定义。
Elasticsearch能够了解您所使用的文档的结构索引(反射)并自动创建映射定义(明确映射创建)。
需要一个正在运行的Elasticsearch安装,要执行这些命令,可以使用任何HTTP客户端,例如curl(https://curl.haxx.se/),postman(https://www.getpostman.com/)或其他类似的平台。 我建议使用Kibana控制台提供更好的代码完成功能Elasticsearch的字符转义。
您可以通过在Elasticsearch中添加新文档来显式创建映射。 对于
为此,我们将执行以下步骤:
1.像这样创建一个索引:
PUT test
获得回复如下:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "test"
}
2.将文档放入索引,如以下代码所示:
PUT test/_doc/1
{
"name":"Paul",
"age":35
}
获得回复如下:
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
3.使用以下代码获取映射:
GET test/_mapping
4.Elasticsearch自动创建的结果映射应为如下:
{
"test" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
5.要删除索引,可以调用以下命令:
DELETE test
获得回复如下:
{
"acknowledged" : true
}
第一个命令行创建一个索引,在此我们将配置类型/映射和插入文件。
第二个命令在索引中插入一个文档(我们将看到索引的创建在第3章,基本操作中的创建索引得文档操作在第3章,基本操作中的索引文档操作介绍)。
在文档索引阶段,Elasticsearch在内部检查_doc类型存在,否则将动态创建一个。
Elasticsearch读取映射字段的所有默认属性,并开始处理它们如下:
在Elasticsearch中,按类型分隔文档是逻辑的,而不是物理的。 Elasticsearch核心引擎透明地对此进行管理。 从物理上来说,所有文件
类型使用相同的Lucene索引,因此它们之间没有完全分隔的,类型的概念纯属逻辑,由Elasticsearch强制执行。 用户不是对此内部管理感到困扰,但在某些情况下,数量的记录,这会影响读写性能记录,因为所有记录都存储在相同的索引文件中。
每个文档都有一个唯一的标识符,称为索引的UID,该标识符存储在
文档的特殊_uid字段。 这是通过将_id的文档类型。 在我们的示例中,_uid将是_doc#1。
可以在索引时间提供_id,也可以由_id自动分配Elasticsearch(如果缺少)。
创建或更改映射类型时,Elasticsearch自动传播将更改映射到集群中的所有节点,以便将所有分片对齐处理该特定类型
每个索引只能包含一个类型。 类型的名称Elasticsearch的先前版本可能会有所不同。 因为类型是在7.x中已弃用,最佳做法是调用_doc类型。
使用显式映射可以更快地开始提取数据,使用无模式方法,而不必担心字段类型。 因此,取得更好的索引结果和性能,则需要手动定义映射。
微调映射带来了一些优势,例如:
Elasticsearch允许您使用具有配置的广泛基础字段。
让我们使用类似eBay的商店的车间订单的半现实示例:
1.首先,我们定义一个表格:
Name | Type | Description |
---|---|---|
id | identifier | 订单识别码 |
date | date(time) | 订购日期 |
customer_id | id reference | 客户编号 |
name | string | 商品的名称 |
quantity | integer | 商品的数量 |
price | double | 商品的价格 |
vat | double | 商品的增值税 |
sent | boolean | 订单状态 |
2.我们的订单记录必须转换为Elasticsearch映射定义如下:
#前提是索引存在,不然会报错,解决办法:PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword"},
"sent" : {"type" : "boolean"},
"name" : {"type" : "keyword"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double", "index":"false"}
}
}
# 如果索引不存在就创建索引及其对应映射
PUT test
{
"mappings": {
"properties": {
"id":{"type": "keyword"},
"date":{"type": "date"},
"customer_id":{"type": "long"},
"name":{"type": "keyword"},
"quantity":{"type": "integer"},
"price":{"type": "double"},
"vat":{"type": "double","index":"false"},
"sent":{"type": "boolean"}
}
}
}
现在,可以将映射放入索引中了。 我们将在在第4章“基本操作”中,将映射放入索引配置中。
字段类型必须映射到Elasticsearch基本类型之一,并且有关的选项
需要添加字段必须如何编制索引。
下表是映射类型的参考:
Type | ES-Type | Description |
---|---|---|
String, VarChar | keyword | 这是一个不可标记的文本字段:CODE001 |
String, VarChar,Text | text | 这是一个需要标记的文本字段:一个漂亮的文本 |
Integer | integer | 这是一个整数(32位):1、2、3或4 |
long | long | 这是一个长值(64位) |
float | float | 这是一个浮点数(32位):1.2或4.5 |
double | double | 这是一个浮点数(64位) |
boolean | boolean | 这是一个布尔值:true或false |
date/datetime | date | 这是日期或日期时间值:2013-12-25,2013-12-25T22:21:20 |
bytes/binary | binary | 这包括一些用于二进制的字节数据,例如文件或字节流 |
根据数据类型,可以向Elasticsearch给出明确的指令处理以进行更好的管理时。 最常用的选项是如下:
Boost仅在术语级别上工作,因此主要用于术语,术语,并匹配查询。
在Elasticsearch的先前版本中,字符串的标准映射为字符串。 在版本5.x中,不建议使用字符串映射并将其迁移到关键字和文本映射。
仅适用于文本映射的另一个重要参数是term_vector(组成字符串的术语的向量。请参阅Lucene文档以了解有关更多详细信息,请参见http://lucene.apache.org/core/6_1_0/core/org/apache/lucene/index/Terms.html)。
term_vector可以接受以下值:
词项向量允许快速突出显示,但由于存储其他文本信息而占用磁盘空间。 最佳做法是仅在需要突出显示的字段(例如标题或文档内容)中激活。
数组或多值字段在数据模型中非常常见(例如多个电话号码,地址,姓名,别名等),但本机不支持传统的SQL解决方案。
在SQL中,多值字段要求创建必须连接的附件表收集所有值,导致记录基数降低时的影响性能很大
1.每个字段都自动作为数组进行管理。 例如,存储标签对于文档,映射将如下所示:
{
"properties" : {
"name" : {"type" : "keyword"},
"tag" : {"type" : "keyword", "store" : "yes"},
...
}
}
2.此映射对两个文档都有效。 以下是文档1的代码:
{"name": "document1", "tag": "awesome"}
3.以下是document2的代码:
{"name": "document2", "tag": ["cool", "awesome", "amazing"] }
Elasticsearch透明地管理数组:如果声明一个a,则没有区别,由于其Lucene核心性质,因此是单一值或多值。
字段的多值是在Lucene中管理的,因此您可以将它们添加到具有相同字段名称的文档。 对于具有SQL背景的人来说,行为可能很奇怪,但这是NoSQL世界中的关键点,因为它减少了对联接查询的需求,并创建了不同的表来管理多值。 嵌入式对象数组的行为与简单字段相同。
对象是基本结构(类似于SQL中的记录)。 Elasticsearch扩展了传统上使用对象,因此允许递归嵌入对象。
我们可以使用来重写上一个示例配置中找到的映射代码项目数据:
PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "object",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
或者
PUT test/_mapping?include_type_name=true
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "object",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
遇见问题:
Types cannot be provided in put mapping requests, unless the include_type_name parameter is set to t...
这个是因为elasticsearch7.0 之后不支持type导致的…
原因是由于写法是低版本的elasticsearch的,高版本要求传入一个include_type_name参数,值为true。所以加上一个参数即可。如下:
PUT 192.168.2.121:9200/cs_company/doc/_mapping?include_type_name=true
Elasticsearch使用本机JSON,因此可以映射每个复杂的JSON结构进去。当Elasticsearch解析对象类型时,它将尝试提取字段和进程它们作为其定义的映射。如果不是,它将使用以下命令学习对象的结构反射。对象的最重要属性如下:
最常用的属性是properties,它允许您映射Elasticsearch字段中的对象。
禁用文档的索引部分会减小索引的大小;但是,那无法搜索数据。换句话说,最终在磁盘上的文件较小,但是功能上要付出代价。
有一种特殊类型的嵌入式对象:嵌套对象。 这样可以解决问题与Lucene索引架构有关,其中嵌入式对象的所有字段被视为单个对象。 在搜索过程中,在Lucene中无法区分值和同一多值中的不同嵌入对象数组。
如果考虑上一个订购示例,则无法区分商品名称和数量与相同的查询相同,就像Lucene将它们放在相同的Lucene中一样文档对象。 我们需要将它们编入不同的文档中,然后再加入它们。整个行程由嵌套对象和嵌套查询管理。
为什么要有嵌入对象:
这是因为Lucene底层其实没有内部对象的概念,所以ES会利用简单的列表储存字段名和值,将object类型的对象层次摊平,再传给Lucen。
如果需要索引对象数组并维护数组中每个对象的独立性,则应使用nested
数据类型而不是object
数据类型。 在内部,嵌套对象将数组中的每个对象作为单独的隐藏文档进行索引,这意味着每个嵌套对象都可以使用嵌套查询nested query
独立于其他对象进行查询:
参考案例:
#采用object的方式
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d'
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
#在内部其转换成一个看起来像下面这样的文档
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
#采用nested
curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
"mappings": {
"my_type": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
}
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d'
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
'
嵌套对象定义为具有嵌套类型的标准对象。从映射对象配方的示例中,我们可以更改对象的类型嵌套如下:
PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "nested",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
对文档建立索引后,如果将嵌入式对象标记为嵌套,则该对象为由原始文档提取,然后在新的外部索引中文档,保存在父文档附近的特殊索引位置。
在前面的示例中,我们重用了“映射对象”配方的映射,但是我们将项目的类型从对象更改为嵌套。 无需其他任何操作必须将嵌入式对象转换为嵌套对象。
嵌套的对象是特殊的Lucene文档,它们保存在父文档的数据-这种方法允许与父文档快速连接。
嵌套对象不能用标准查询搜索,只能用嵌套对象搜索。 他们不会显示在标准查询结果中。
嵌套对象的生命与其父对象有关:删除/更新父对象自动删除/更新所有嵌套子代。 改变父母的手段Elasticsearch将执行以下操作:
有时,需要将嵌套对象的信息传播到其父对象或根对象。 这主要是为了建立有关父母的简单查询(例如术语查询而不使用嵌套查询)。 为了实现这个目标,有两个特殊的必须使用的嵌套对象的属性:
在上一个cookbook中,我们了解了如何管理之间的关系具有嵌套对象类型的对象。 嵌套对象的缺点是它们的嵌套来自父母的依赖。 如果您需要更改嵌套对象的值,则可以需要重新索引父级(如果嵌套,这会带来潜在的性能开销对象变化太快)。 为了解决这个问题,Elasticsearch允许您定义子文件。
父子档的原因:
嵌套文档来说,父-子关系的主要优势有:
更新父文档时,不会重新索引子文档。
创建,修改或删除子文档时,不会影响父文档或其他子文档。这一点在这种场景下尤其有用:子文档数量较多,并且子文档创建和修改的频率高时。
子文档可以作为搜索结果独立返回。
在下面的示例中,我们有两个相关的对象:一个Order和一个Item。
它们的UML表示如下:
最终的映射应该是Order和Item的字段定义的合并,加上一个特殊字段(在此示例中为join_field),该字段接受父/子关系。
映射将如下所示:
PUT test1/_mapping
{
"properties": {
"join_field": {
"type": "join",
"relations": {
"order": "item"
}
},
"id": {
"type": "keyword"
},
"date": {
"type": "date"
},
"customer_id": {
"type": "keyword"
},
"sent": {
"type": "boolean"
},
"name": {
"type": "text"
},
"quantity": {
"type": "integer"
},
"vat": {
"type": "double"
}
}
}
前面的映射与前面的配方非常相似。
如果要存储联接的记录,则需要先保存父记录,然后再保存子文档信息这样:
PUT test1/_doc/1?refresh
{
"id": "1",
"date": "2018-11-16T20:07:45Z",
"customer_id": "100",
"sent": true,
"join_field": "order"
}
PUT test1/_doc/c1?routing=1&refresh
{
"name": "tshirt",
"quantity": 10,
"price": 4.3,
"vat": 8.5,
"join_field": {
"name": "item",
"parent": "1"
}
}
子项需要特殊管理,因为我们需要添加与父母id的关联。 此外,在对象中,我们需要指定父名称和它的ID。
如果在同一索引中存在多个项目关系,则需要进行映射计算为所有其他映射字段的总和。
对象之间的关系必须在join_field中定义。只能有一个join_field进行映射; 如果你需要付出很多关系,可以在关系对象中提供它们。
子文档必须在父文档的同一分片中建立索引; 所以,当建立索引后,必须传递一个额外的参数,该参数用于路由(我们将看看如何做在下一章的索引文档配方中对此进行说明)。
当我们想要子文档不需要重新索引父文档时更改其值。 因此,它在建立索引,重新建立索引(更新)和删除。
在Elasticsearch中,我们有不同的方法来管理对象之间的关系,例如
如下:
选择如何从对象建模关系取决于您的应用程序场景。
还有另一种方法可以使用,但是在大数据文档中,它带来了表现不佳-连接关系脱钩。 您可以通过两个步骤来执行联接查询:首先,您收集子文档/其他文档的ID,然后搜索它们在他们父母的领域。