09 文档(document)操作


文档是具体的数据,类似于数据库中的一条数据,文档必须包含在一个索引中

1 新增文档

需要注意的是,如果文档的id相同,在es中就是更新这个id的文档。

1.1 指定id创建

基本请求格式

put index/type/id
{
    field1:value1,
    field2:value2  
}

在新版的es中已经取消type的设定,8.0以后的已经完全移除了。

之前创建的blog索引中创建一个文档,type为article

请求

PUT blog/article/1
{
  "blog_type":"IT",
  "create_at":"2020-01-23T09:32:03",
  "content":"面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"
}

响应

#! Deprecation: [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}).
{
  "_index" : "blog",
  "_type" : "article",
  "_id" : "1", # 返回文档的id
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 3,  # 返回是文档被创建的时候,在多少个分片中进新货了操作,
    包括主分片和副本分片,
    由于这里是写入操作,自然只在三个主分片中操作,当时创建的时候设置的就是3个主分片,主分片有2个副本分片
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

返回的结果也开始提醒: Specifying types in document index requests is deprecated(不建议在文档索引请求中指定type)
请使用一下endpoint代替

  • /{index}/_doc/{id}
  • /{index}/_doc 这个和第一个的区别就是不指定ID,交给es自动生成id
  • /{index}/_create/{id})

/{index}/_doc/{id}

请求

PUT blog/_doc/2
{
  "blog_type":"IT",
  "create_at":"2020-01-24T09:32:03",
  "content":"封装:函数的封装是一种形式,隐藏对象的属性和实现细节(函数内部),仅仅对外提高函数的接口和对象进行交互。"
}

响应

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "2", 
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

/{index}/_create/{id})

请求

PUT blog/_create/3
{
  "blog_type":"IT",
  "create_at":"2020-01-25T09:32:03",
  "content":"Redis是一个开源的 高性能键值对数据库。他通过 提供多种键值数据类型来适应不用场景下的存储需求,并借助许多高层级的接口使其 可以胜任如缓存、队列系统等不同的角色。"
}

响应

{
  "_index" : "blog",
  "_type" : "_doc", # 注意不管是_create还是_doc 我们的type都是_doc
  "_id" : "3",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

注意不管是_create还是_doc 我们的type都是_doc, 可见这两个操作都是这个type

1.2 不指定指定id创建

基本请求格式

POST index/_doc
POST index/_create
{
    field1:value1,
    field2:value2  
}

演示

请求

POST blog/_doc
{
  "blog_type":"IT",
  "create_at":"2020-01-26T09:32:03",
  "content":"Kibana是ES的一个配套工具,让用户在网页中可以直接与ES进行交互。"
}

响应

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "LghOE3YBf-nZVOC8upj2",  # 自动生成id
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

1.3 自动创建索引

当创建文档的时候,如果索引不存在,则会自动创建索引。自动创建的索引会自动映射每个字段的类型。自动创建字段类型是很灵活的,新的字段类型,将会自定匹配字段对象的类型。

1.4 版本号

每一个文档都有一个版本号,版本号具体的值放在创建文档的返回中(_version)。通过版本好可以达到并发控制的效果,当在操作文档的过程中指定版本号,如果和版本号不一致的时候,就会被拒绝。版本号通常用在对事务的处理中。

演示:更新刚刚创建的“封装:函数的封装是一种形式,隐藏对象的属性和实现细节(函数内部),仅仅对外提高函数的接口和对象进行交互。” 这个文档返回的版本号是1

请求

PUT blog/_doc/2?version=999
{
  "blog_type":"IT",
  "create_at":"2020-01-24T09:32:03",
  "content":"封装:函数的封装是一种形式,隐藏对象的属性和实现细节(函数内部),仅仅对外提高函数的接口和对象进行交互。"
}

响应

{
  "error": {
    "root_cause": [
      {
        "type": "action_request_validation_exception",
        "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
      }
    ],
    "type": "action_request_validation_exception",
    "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
  },
  "status": 400
}

optimistic concurrency control 乐观并发控制

注意: 版本号是实时更新的,不会存在缓存现象。

默认版本号是从1开始递增的,包括修改和删除文档。当然版本号还可以从外部获取,比如从数据库中获取,要启用此功能,version_type应该设置为external

此时必须自己指定版本号version=1

PUT blog3/_doc/1?version_type=external&version=1
{
  "blog_type":"IT",
  "create_at":"2020-01-23T09:32:03",
  "content":"面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"

}

这个值必须是一个大于0小于9.2e+18的数字。

当时使用外部版本号代替自动生成版本号时,在操作文档的时候,系统通过对比参数中的版本号是否大于文档中的版本号来做判断。大于则执行该操作,并更新版本号,反之则拒绝操作。

version type 可以有一下几种:

  • internal: 仅在给定版本与所存储文档的版本相同时才对文档建立索引。
  • external or external_gt: 仅当给定版本严格高于存储文档的版本或没有现有文档时,才对文档建立索引。 给定的版本将用作新版本,并将与新文档一起存储。 提供的版本必须为非负长整数。
  • external_gte: 仅当给定版本等于或高于存储文档的版本时,才对文档建立索引。 如果没有现有文档,该操作也将成功。 给定的版本将用作新版本,并将与新文档一起存储。 提供的版本必须为非负长整数。

external_gte版本类型用于特殊用例,应谨慎使用。 如果使用不当,可能会导致数据丢失。 还有另一种选择,即force,它已被弃用,因为它可能导致主分片和副本分片分开。

1.5 操作类型

op_type参数强制命令执行创建操作,默认只有索引中不存在该文档才会执行创建动作,当存在的时候,是更新该文档。

可以指定op_type=create来进行强制创建。

比如再次创建id为2的文档

PUT blog/_doc/2?op_type=create
{
  "blog_type":"IT",
  "create_at":"2020-01-24T09:32:03",
  "content":"封装:函数的封装是一种形式,隐藏对象的属性和实现细节(函数内部),仅仅对外提高函数的接口和对象进行交互。"
}

响应:document already exists

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[2]: version conflict, document already exists (current version [1])",
        "index_uuid": "Pk4d0oFfSlqM_0Y4cf5TuQ",
        "shard": "1",
        "index": "blog"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[2]: version conflict, document already exists (current version [1])",
    "index_uuid": "Pk4d0oFfSlqM_0Y4cf5TuQ",
    "shard": "1",
    "index": "blog"
  },
  "status": 409
}

1.6 分片选择

路由算法
shard = hash(routing) % number_of_primary_shards

对一个文档经行crud时,都会带一个路由值 routing number。默认为文档_id(可能是手动指定,也可能是自动生成)。

  • 存储1号文档,经过哈希计算,哈希值为2,此索引有3个主分片,那么计算2%3=2,就算出此文档在P2分片上。
  • 决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的
  • 无论hash值是几,无论是什么数字,对number_of_primary_shards求余数,结果一定是在0~number_of_primary_shards-1之间这个范围内的。0,1,2。

默认情况下,分片选择是通过ID的散列值进行控制的,这个只可以通过router参数进行手动控制。可以在每个操作的基础上通过哈希函数的值来指定分片选择。
比如:

PUT blog/_doc/4?routing=helloworld
{
  "blog_type":"IT",
  "create_at":"2020-01-24T09:32:03",
  "content":"哈希表也称散列表,是一种数据结构,它可以提供快速的插入操作和查找操作,不论有多少数据项,插入与删除只需要接近常量的时间:O(1)时间级。但哈希表也有缺点,它是基于数组的,数组一旦被创建,就难以扩展。某些哈希表被填满时,性能急剧下降"
}

就是根据helloworld的哈希值来确定的

1.7 其他说明

  • 分布式:索引操作主要是针对主分片进行,当主分片的索引操作完成之后,如果有副本分片就会分发到副本中
  • 一致性:为了防止当网络出现问题时写入不一致,es只有在有效节点数量大于一定数量的时候才生效
  • 刷新:写入的时候,可以指定refresh参数为true来立即刷新所有副本,当refresh参数为true,es做了足够的优化,不会对系统产生任何影响,但是查询的时候这个参数是没有任何意义的。
  • 空操作:当文档内容没有发生任何改变的时候,更新文档操作也会生效,但是版本号不会改变。如果希望版本号发生变化,在更新的时候可以指定detect_noop参数为true。这个参数在创建的时候是无效的。
  • 超时:默认超时时间是1分钟,可以通过设置timeout参数来修改超时时间,比如timeout=5m,表示超时时间为5分钟

2 ID查询文档

2.1 入门

请求格式

GET /{索引}/_doc/{id}

查询blog索引刚刚创建id为2的文档

请求

GET /blog/_doc/2

响应

{
  "_index" : "blog",  # 所属的索引库
  "_type" : "_doc", # type
  "_id" : "2", # 文档id
  "_version" : 1, # 版本号
  "_seq_no" : 0,
  "_primary_term" : 1, 
  "found" : true, # 是否发现
  "_source" : {
    "blog_type" : "IT",
    "create_at" : "2020-01-24T09:32:03",
    "content" : "封装:函数的封装是一种形式,隐藏对象的属性和实现细节(函数内部),仅仅对外提高函数的接口和对象进行交互。"
  }
}
  • _source: 文档信息

2.2 禁用实时性

默认查询获得的数据是实时的,可以通过realtime设置为false,来关闭实时性

GET /blog/_doc/2?realtime=false

2.3 _source

2.3.1 不返回_source字段

请求中指定_source参数为false

请求

GET blog/_doc/2?_source=false

响应

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 2,
  "_seq_no" : 3,
  "_primary_term" : 3,
  "found" : true
}
2.3.1 只返回_source字段(只获取文档内容)

请求

GET blog/_doc/2/_source

响应

{
  "blog_type" : "IT",
  "create_at" : "2020-01-23T09:32:03",
  "content" : "面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"
}
2.3.2 过滤_source

可以通过_source_exclude_source_include过滤掉返回的字段

比如:只返回create_atcontent 多个的时候使用,分割

请求

GET blog/_doc/2?_source_includes=create_at,content

响应

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 2,
  "_seq_no" : 3,
  "_primary_term" : 3,
  "found" : true,
  "_source" : {
    "create_at" : "2020-01-23T09:32:03",
    "content" : "面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"
  }
}

2.4 分片选择

可以在查询的时候,指定路由选择(routing),当路由不存在的时候,返回为空值

GET blog/_doc/2?routing=helloworld

使用helloworld的哈希值来路由。

2.5 其他查询参数

  • refresh=true: 设置为true,这样搜索操作前,会刷新相关的分片保证可以及时查询到。但是这个参数会消耗系统资源,除非有必要,正常情况下不需要设置。
GET blog/_doc/2?refresh=true
  • realtime=true:默认情况下(true),get API是实时的,并且不受索引刷新率的影响(当数据对搜索可见时)。 如果文档已更新但尚未刷新,则get API将就地发出刷新调用以使文档可见。 自上次刷新以来,这还将使其他文档发生更改。 为了禁用实时GET,可以将realtime参数设置为false。
  • timeout=5m :设置此次请求的超时时间

2.6 多文档查询

多文档查询指的是可以在同一个接口中分别指定index,type,id来进行多个文档查询

请求格式

GET _mget
{
  "docs":[
    {
      "_index":"索引名",
      "_type":"类型名",
      "_id":id
    },
    {
      "_index":"索引名",
      "_type":"类型名", # 可以指定上面不同的type和index
      "_id":id
    }
  ]
}

比如查询blog索引中的文档

请求

GET _mget
{
  "docs":[
    {
      "_index":"blog",
      "_type":"_doc",
      "_id":2
    },
    {
      "_index":"blog",
      "_type":"article",
      "_id":1
    }
  ]
}

响应

{
  "docs" : [
    {
      "_index" : "blog",
      "_type" : "_doc",
      "_id" : "2",
      "_version" : 2,
      "_seq_no" : 3,
      "_primary_term" : 3,
      "found" : true,
      "_source" : {
        "blog_type" : "IT",
        "create_at" : "2020-01-23T09:32:03",
        "content" : "面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"
      }
    },
    {
      "_index" : "blog",
      "_type" : "article",
      "_id" : "1",
      "_version" : 3,
      "_seq_no" : 2,
      "_primary_term" : 3,
      "found" : true,
      "_source" : {
        "blog_type" : "IT",
        "create_at" : "2020-01-23T09:32:03",
        "content" : "面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言。"
      }
    }
  ]
}

还可以简化:index和type是可以直接放到url上的,

  • 比如查询的是同一个index的时候(刚刚查询都是blog)
GET blog/_mget
{
  "docs":[
    {
      "_type":"_doc",
      "_id":2
    },
    {
      "_type":"article",
      "_id":1
    }
  ]
}
  • 比如都是查询同一个index和type的时候,可以将type也放到url上
GET blog/_doc/_mget
{
  "docs":[
    {
      "_id":2
    },
    {
      "_id":1
    }
  ]
}

或者:

GET blog/_doc/_mget
{
 "ids":[1,2]
}

3 更新文档

文档被索引之后,如果要更新,那么es内部会先找到该文档,删除旧文档,执行更新,更新完成后再索引新的文档

前面也说了es会根据文档是否存在来进行处理,存在就是更新,不存在就进行创建

请求格式和创建文档是一样的

put index/type/id
{
    field1:value1,
    field2:value2 
}
  1. 查询一下id为3的
GET blog/_doc/3
{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "3",
  "_version" : 1,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "blog_type" : "IT",
    "create_at" : "2020-01-25T09:32:03",
    "content" : "Redis是一个开源的 高性能键值对数据库。他通过 提供多种键值数据类型来适应不用场景下的存储需求,并借助许多高层级的接口使其 可以胜任如缓存、队列系统等不同的角色。"
  }
}
  1. 修改一下,将blog_type改为美食

请求

PUT blog/_doc/3
{
  "blog_type" : "美食"
}

响应

{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "3",
  "_version" : 2, # 版本号自动加1
  "result" : "updated",
  "_shards" : {
    "total" : 3,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 4,
  "_primary_term" : 3
}
  1. 再次查询: 但是,这里有个问题,在查询这条文档,会发现其他字段都没了
{
  "_index" : "blog",
  "_type" : "_doc",
  "_id" : "3",
  "_version" : 2,
  "_seq_no" : 5,
  "_primary_term" : 3,
  "found" : true,
  "_source" : {
    "blog_type" : "美食"
  }
}

其实这里的更新就是如果id存在,删除旧的,创建新的,并非严格意义的更新

4 删除文档

DELETE index/type/id

5 批量操作( bulk api)

使在单个API调用中执行许多索引/删除操作成为可能。 这样可以大大提高索引速度。

这个api得body得内容不是严格的json格式

Bulk格式包含请求行为action和请求数据requestbody,这两个是一条命令,但是要换行,如下:

官网给的一个样例

POST _bulk
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

其中bulk有一下四个action

  • create(文档不存在时创建)、
  • update(更新文档)、
  • index(创建新文档或替换已有文档)、
  • delete(删除一个文档)。
  • create和index的区别:如果数据存在,使用create操作失败,会提示文档已经存在,使用index则可以成功执行。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值