文档基本操作
# 文档基本操作
在 Kibana 的 Dev Tools 中,以在线书店业务为例,展示 DSL 语法。
# 索引管理
文档是存在于索引里面的,所以要先创建一个索引并定义其 Mapping(就像使用 MySQL 数据库插入数据前,肯定要先创建表)。
现在定义如下 Mapping,并且创建索引(主要字段:书本的 ID、名字、作者、简介):
# 创建 books 索引
PUT books
{
"mappings": {
"properties": {
"book_id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"author": {
"type": "keyword"
},
"intro": {
"type": "text"
}
}
},
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
# 返回结果
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "books"
}
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
上述操作创建了 books 索引,它包含 book_id
(书本 ID)、name
(书本名字)、author
(作者)、intro
(简介)四个字段。其中 books 索引还有 3 个分片和 1 个副本备份。
如果之前已经创建过 books 索引的话,这里再次创建会报错,此时需要先将之前的索引删除,然后再重新创建。在 Kibana 执行以下示例可以删除索引:
# 删除索引 books
DELETE books
# 返回结果
{
"acknowledged" : true
}
2
3
4
5
6
7
# 新建文档
ES 提供了两种创建文档的方式,一种是使用 Index API 索引文档,一种是使用 Create API 创建文档。
# 使用 Index API 索引文档
# 使用 Index API 索引文档
PUT books/_doc/1
{
"book_id": "4ee82462",
"name": "深入Linux内核架构",
"author": "Wolfgang Mauerer",
"intro": "内容全面深入,领略linux内核的无限风光。"
}
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
索引一个文档比较简单,将 Mapping 中对应的字段做成 Json Object 对应的 Key 即可,并且上面的例子中我们指定文档 ID 为 1。需要说明的是,在 ES 7.0 版本后 type
统一为 _doc
。
如果索引的文档已经存在,比如在不改变文档 ID 的情况下多次执行上面的索引文档的语句,系统并不会报错,而是将返回结果中 _version
字段的值自加。
那是因为在索引一个文档的时候,如果文档 ID 已经存在,会先删除旧文档,然后再写入新文档的内容,并且增加文档版本号。
# 使用 Create API 创建文档
使用 Create API 创建文档有两种写法:PUT 和 POST 方式,其示例分别如下:
使用 PUT 的方式创建文档需要指定文档的 ID,如果文档 ID 已经存在,则返回 http 状态码为 409 的错误
# 使用 PUT 的方式创建文档
PUT books/_create/2
{
"book_id": "4ee82463",
"name": "时间简史",
"author": "史蒂芬霍金",
"intro": "探索时间和空间核心秘密的引人入胜的故事。"
}
# PUT 方式返回的结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 1
......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用 POST 的方式创建文档时候,则不需要指定文档 ID,系统会自动创建。
# 使用 POST 的方式,不需要指定文档 ID,系统自动生成
POST books/_doc
{
"book_id": "4ee82464",
"name": "时间简史(插画版)",
"author": "史蒂芬霍金",
"intro": "用精美的插画带你探索时间和空间的核心秘密"
}
# POST 方式返回的结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "LfwVtH0BxOuNtEd4yM4F",
"_version" : 1
......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 总结三种创建文档方式
序号 | 语句 | 特性描述 |
---|---|---|
1 | PUT books/_doc/1 | 使用 Index API 索引文档,如果文档存在,会先删除然后再写入,即有覆盖原内容的功能。 |
2 | PUT books/_create/2 | Create API 中使用 PUT 的方式创建文档,需要指定文档 ID。如果文档已经存在, 则返回 http 409 错误。 |
3 | POST books/_doc | Create API 中使用 POST 的方式,不需要指定文档 ID,系统自动生成。 |
- 如果有更新文档内容的需求,应该使用第一种方式。
- 如果写入文档时有唯一性校验需求的话,应该使用第二种方式。
- 如果需要系统为你创建文档 ID,应该使用第三种方式。
相对于第一种方式来说,第三种方式写入的效率会更高,因为不需要在库里查询文档是否已经存在,并且进行后续的删除工作。
# 获取文档
可以使用 ES 提供的 GET API 来获取文档内容,获取文档有 2 种情况,一种是只获取一个文档内容,另一种是同时获取多个文档的内容。
# 使用 GET API 获取单个文档
通过文档的 ID 获取文档的信息时可以使用 GET API 来实现,其示例如下:
# 使用 GET API 获取单个文档的内容
GET books/_doc/1
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"book_id" : "4ee82462",
"name" : "深入Linux内核架构",
"author" : "Wolfgang Mauerer",
"intro" : "内容全面深入,领略linux内核的无限风光。"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果获取如果成功,文档的原生内容保存在 _source
字段中,其他字段是这个文档的元数据。
GET API 提供了多个参数,更多的信息可以参考官方文档 (opens new window),下面是几个比较常用的:
参数 | 简介 |
---|---|
preference | 默认的情况下,GET API 会从多个副本中随机挑选一个,设置 preference 参数可以控制 GET 请求被路由到哪个分片上执行。 |
realtime | 控制 GET 请求是实时的还是准实时的,默认为 true 。 |
refresh | 是否在执行 GET 操作前执行 refresh (默认的情况下新写入的数据需要一秒后才能被搜索到),默认为 false 。 |
routing | 自定义 routing key。 |
stored_fields | 返回在 Mapping 中 store 设置为 true 的字段,而不是 _source 。默认为 false 。 |
_source | 指定是否返回 _source 的字段,或者设置某些需要返回的字段。 |
_source_excludes | 不返回哪些字段,逗号分割的字符串列表。如果 _source 设置为 false ,此参数会被忽略。 |
_source_includes | 返回哪些字段,逗号分割的字符串列表。如果 _source 设置为 false ,此参数会被忽略。 |
version | 指定版本号,如果获取的文档的版本号与指定的不一样,返回 http 409。 |
# 使用 MGET API 获取多个文档
当需要通过多个文档 ID 同时获取它们的信息时,可以使用 GET API 发起多个请求,但效率比较低下。这时可以使用 ES 提供的 MGET API 来解决这个需求。
MGET API 的请求格式有 3 种,其示例如下:
# 1:在 body 中指定 index
GET /_mget
{
"docs": [
{ "_index": "books", "_id": "1" },
{ "_index": "books", "_id": "2" }
]
}
2
3
4
5
6
7
8
# 2:直接指定 index
GET /books/_doc/_mget
{
"docs": [
{ "_id": "1" },
{ "_id": "2" }
]
}
2
3
4
5
6
7
8
# 3:也可以简写为一下例子
GET /books/_mget
{
"ids" : ["1", "2"]
}
2
3
4
5
# 结果
{
"docs" : [
{
"found" : true,
"_source" : {
"book_id" : "4ee82462",
"name" : "深入Linux内核架构"
......
}
},
{
"found" : true,
"_source" : {
"book_id" : "4ee82463",
"name" : "时间简史",
......
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如上示例,如果在 body 中指定 index,可以获取多个索引中的文档数据,比较灵活。而使用直接指定 index 的方式只能获取指定索引中的文档。更多的 MGET API 使用例子可以参考官方文档 (opens new window)。
如果对应的文档找不到,found
字段为 false
。
# 更新文档
ES 提供了 Update API 来更新文档信息,更新一个文档,需要指定文档的 ID 和需要更新的字段与其对应的值。Update API 使用如下:
# 更新文档
POST books/_update/2
{
"doc": {
"name":"时间简史(视频版)",
"intro": "探索时间和空间核心秘密的引人入胜的视频故事。"
}
}
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 3,
"result" : "updated",
......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
更新文档后,版本号会增加,result
字段为 updated
。
前面提到的,索引文档的方式也有更新数据的效果,那它跟文档更新接口有啥区别呢?
- 索引文档的更新效果是先删除数据,然后再写入新数据。所以索引文档的方式会覆盖旧的数据,使其无法实现只更新某些字段的需求。
# 删除文档
ES 提供了 Delete API 来删除一个文档,删除一个文档是非常简单的,只需要指定索引和文档 ID 即可。Delete API 使用如下:
# 删除 id 为 2 的文档
DELETE books/_doc/2
# 结果
{
"_index" : "books",
"_type" : "_doc",
"_id" : "2",
"_version" : 4,
"result" : "deleted",
......
}
2
3
4
5
6
7
8
9
10
11
12
除了指定文档 ID 进行文档删除外,我们还可以使用 Delete By Query API 进行查询删除。
# 使用 Delete By Query API 删除文档
POST /books/_delete_by_query
{
"query": {
"term": {
"book_id": "4ee82462"
}
}
}
# 结果
{
"total" : 1,
"deleted" : 1,
......
"failures" : [ ]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
如上示例,将删除掉所有满足查询条件的内容。在返回结果中,deleted
字段为 1
,即删除了 1
个文档。
# 批量操作文档
当我们需要写入多个文档的时候,如果每写一个文档就发起一个请求的话,多少有点浪费。这个时候我们可以使用 Bulk API 来批量处理文档。
Bulk API 支持在一次调用中操作不同的索引,使用时可以在 Body 中指定索引也可以在 URI 中指定索引。而且还可以同时支持 4 种类型的操作:
- Index
- Create
- Update
- Delete
Bulk API 的格式是用换行符分隔 JSON 的结构,第一行指定操作类型和元数据(索引、文档 id 等),紧接着的一行是这个操作的内容(文档数据,如果有的话。像简单的删除就没有。),其格式如下:
POST _bulk
# 第一行指定操作类型和元数据(索引、文档id等)
{ "index" : { "_index" : "books", "_id" : "1" } }
# 紧接着的一行是这个操作的内容(文档数据,如果有的话。像简单的删除就没有)
{ "book_id": "4ee82462","name": "深入Linux内核架构", ......}
2
3
4
5
下面示例是在 Bulk API 中同时使用多种操作类型的例子:
# 在 Bulk API 中同时使用多种操作类型的实例
POST _bulk
{ "index" : { "_index" : "books", "_id" : "1" } }
{ "book_id": "4ee82462","name": "深入Linux内核架构","author": "Wolfgang Mauerer","intro": "内容全面深入,领略linux内核的无限风光。" }
{ "delete" : { "_index" : "books", "_id" : "2" } }
{ "create" : { "_index" : "books", "_id" : "3" } }
{ "book_id": "4ee82464","name": "深入Linux内核架构第三版","author": "Wolfgang Mauerer","intro": "内容全面深入,再次领略linux内核的无限风光。" }
{ "update" : {"_index" : "books", "_id" : "4"} } # 指定操作类型、索引、文档 id
{ "doc" : {"intro" : "书本的内容非常好,值得一看"} } # 指定文档内容
# 结果
{
"items" : [
{
"index" : {
"_id" : "1",
"result" : "created",
......
}
},
{
"delete" : {
"_id" : "2",
"result" : "deleted",
......
}
},
{
"create" : {
"_id" : "3",
"result" : "created",
......
}
},
{
"update" : {
"_id" : "4",
"status" : 404,
......
}
}
]
}
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
39
40
41
42
43
因为一个请求中有多个操作,所以返回结果中会对每个操作有相应的执行结果。如果其中一条操作失败,是不会影响其他操作的执行。
更详细 Bulk API 使用方式,可以参考官方文档 (opens new window)。
# 参考资料
(完)