1. 前言
Elasticsearch索引是一组相关文档的集合,文档在Elasticsearch中用JSON来表示,每个文档都有一个唯一的"_id"字段来标识。每个文档又是一组字段的集合,字段可以有自己的数据类型,可以是数字、字符串、日期、布尔类型等,Elasticsearch可以索引文档,并对索引的文档做检索和数据分析。
2. 新增文档
我们已经知道,每个文档都有一个唯一的"_id"字段标识。新增文档时,我们既可以指定ID,也可以不指定ID,不指定ID时Elasticsearch会自动生成基于Base64编码长度为20的GUID字符串。
下面是指定ID索引文档的请求:
json
PUT items/_doc/1
{
"title":"苹果",
"price":5
}
下面是不指定ID索引文档的请求:
json
POST items/_doc
{
"title":"香蕉",
"price":3
}
可以看到,系统自动生成的ID值为"h1e65Y4BXAgLe9UU1-xS"
json
{
"_index": "items",
"_id": "h1e65Y4BXAgLe9UU1-xS",
"_score": 1,
"_source": {
"title": "香蕉",
"price": 3
}
}
你也许会好奇,items索引压根就不存在,为什么文档可以索引成功呢?这是因为Elasticsearch默认会自动创建索引,可以配置action.auto_create_index
来关闭,如下所示:
json
PUT _cluster/settings
{
"persistent": {
"action.auto_create_index":false
}
}
再次发起一个索引不存在的文档索引请求,会得到一个异常
json
{
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index [items-1] and [action.auto_create_index] is [false]",
"index_uuid": "_na_",
"index": "items-1"
}
],
"type": "index_not_found_exception",
"reason": "no such index [items-1] and [action.auto_create_index] is [false]",
"index_uuid": "_na_",
"index": "items-1"
},
"status": 404
}
Elasticsearch默认会自动创建不存在的索引,并根据索引的文档数据动态映射字段类型,在这个例子中,price被映射为long类型,title被映射为text和keyword多数据类型。
json
GET items/_mapping
{
"items": {
"mappings": {
"properties": {
"price": {
"type": "long"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
除了单文档的添加,Elasticsearch还支持批量添加文档,对应的API是 _bulk,对于大量文档的写入,批量操作可以显著提升性能。如下示例,批量写入两个文档:
json
POST items/_bulk
{"index":{"_id":"1"}}
{"title":"苹果", "price":5}
{"index":{"_id":"2"}}
{"title":"香蕉", "price":3}
文档与文档之间必须通过换行符来分割,除了索引请求,还可以批量处理删除、更新请求,如下请求,它在items索引下添加了两篇文档、删除了一篇文档、同时更新了orders索引下的一篇文档。
json
POST _bulk
{"index":{"_index":"items","_id":"1"}}
{"title":"苹果", "price":5}
{"create":{"_index":"items","_id":"2"}}
{"title":"香蕉", "price":3}
{"update":{"_index":"orders","_id":"1"}}
{"doc":{"order_amount":600}}
{"delete":{"_index":"items","_id":"3"}}
关于create和index的区别是:对于相同的文档,create请求会失败,而index总是会成功,已存在的文档index请求会更新文档。
3. 更新文档
更新文档操作分为:部分更新和全量更新,部分更新即更新文档的部分字段,全量更新则是直接替换整个文档。
部分更新操作,如下示例,把1号文档的title改为"新鲜苹果",price字段仍然会保留
json
POST items/_update/1
{
"doc": {
"title":"新鲜苹果"
}
}
GET items/_doc/1
{
"_index": "items",
"_id": "1",
"_version": 2,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"title": "新鲜苹果",
"price": 5
}
}
全量更新操作会直接覆盖原有文档,本质上是通过index API来完成的,如下示例,price字段会丢失:
json
PUT items/_doc/1
{
"title":"红富士苹果"
}
GET items/_doc/1
{
"_index": "items",
"_id": "1",
"_version": 3,
"_seq_no": 3,
"_primary_term": 1,
"found": true,
"_source": {
"title": "红富士苹果"
}
}
除此之外,Elasticsearch还支持upsert操作,它类似于关系数据库中的insertOrUpdate,如果文档存在则执行更新操作,不存在则执行写入操作。
如下示例,如果不存在ID=1的文档,则写入upsert部分内容,如果存在ID=1的文档,则更新doc部分的内容。
json
POST items/_update/1
{
"doc": {
"place_of_production": "山东烟台"
},
"upsert": {
"title": "苹果",
"price": 5
}
}
第一次请求时没有place_of_production字段,第二次请求时,因为文档已经存在,则会写入place_of_production。
除了根据指定ID更新单个文档,Elasticsearch同样支持根据搜索条件批量更新文档,对应的API是_update_by_query。批量更新文档首先需要提供"query"部分设定要更新文档的匹配条件,再提供"script"部分即文档更新的脚本。
如下示例,"match_all"匹配所有文档,将所有商品的价格上调1元钱
json
POST items/_update_by_query
{
"query": {
"match_all": {}
},
"script": {
"lang": "painless",
"source": "ctx._source.price+=1"
}
}
文档更新结果:
json
[
{
"_index": "items",
"_id": "1",
"_score": 1,
"_source": {
"price": 6,
"title": "苹果"
}
},
{
"_index": "items",
"_id": "2",
"_score": 1,
"_source": {
"price": 4,
"title": "香蕉"
}
}
]
4. 删除文档
删除文档同样分为根据ID单个删除和批量删除文档。需要说明的是,执行删除操作后,Elasticsearch并不会立即将文档从磁盘中物理删除掉,这是因为Elasticsearch采用段的设计来存储文档,每个段都是独立且不可变的索引结构,这么做可以提高查询性能,删除操作仅仅是将文档标记为"已删除",在后续"段合并"阶段Elasticsearch才会真正将这些被标记为已删除的文档真正的删除并释放存储空间。
如下示例,删除items索引下的1号文档:
json
DELETE items/_doc/1
返回结果中,result=deleted代表删除成功,同时_version会自增1。
json
{
"_index": "items",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
还可以根据条件来批量删除文档,对应的API是_delete_by_query,如下示例,将价格大于等于4元的商品全部删除:
json
POST items/_delete_by_query
{
"query": {
"range": {
"price": {
"gte": 4
}
}
}
}
返回结果中,deleted=2代表删除了两篇符合要求的文档
json
{
"took": 37,
"timed_out": false,
"total": 2,
"deleted": 2,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
5. 迁移文档
除了常规的增删改查,Elasticsearch还支持将一个索引的文档迁移到另一个索引中,可以是集群内迁移,也支持跨集群迁移。
对应的API是_reindex,什么时候需要用到reindex呢?索引的一部分配置是静态的,一旦创建好就不允许更改了,比如:主分片的数量,字段的数据类型等,要更改这些静态配置就需要reindex重新构建索引。
举个例子,现在有一个news索引存储新闻数据,包含新闻的标题和评论:
json
POST news/_doc
{
"title": "女子在小区车库遇1米多高阿富汗猎犬",
"comments": [
{
"user":"张三",
"content":"遛狗不拴绳,等于狗遛人"
},
{
"user":"李四",
"content":"那最后是什么处理结果呢"
},
{
"user":"王五",
"content":"大型犬太危险了"
}
]
}
默认情况下,Elasticsearch会把comments字段动态映射为Object类型,comments数组会被构建成一个扁平的键值对数组,从而丢失了单个对象之间的关系,如下所示:
json
{
"comments.user":["张三","李四","王五"],
"comments.content":["遛狗不拴绳,等于狗遛人","那最后是什么处理结果呢","大型犬太危险了"]
}
现在我们要搜索:张三评论了包含"危险"词语的新闻,搜索条件如下所示:
json
GET news/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"comments.user": "张三"
}
},
{
"match": {
"comments.content": "危险"
}
}
]
}
}
}
因为Object类型的关系,文档竟然被意外的返回了,这明显与我们的需求不符,这个时候就需要把comments字段更改为nested数据类型,让comments数组的每个对象都单独存储。这种操作不能在news索引上直接操作,所以我们需要创建一个新的索引,然后通过reindex操作来做数据迁移。
新索引news-nested创建命令如下所示,comments类型改为nested,同时新增一个comments_count字段记录评论数:
json
PUT news-nested
{
"mappings": {
"properties": {
"title":{
"type": "text"
},
"comments":{
"type": "nested"
},
"comments_count":{
"type": "integer"
}
}
}
}
接着,调用reindex API完成数据迁移,指定源索引和目标索引,以及迁移过程中要执行的脚本:
json
POST _reindex
{
"source": {
"index": "news"
},
"dest": {
"index": "news-nested"
},
"script": {
"source": "ctx._source.comments_count = ctx._source.comments.length"
}
}
查看目标索引的数据,发现文档迁移成功,且comments_count字段有了
json
[
{
"_index": "news-nested",
"_id": "lFcm5o4BXAgLe9UUo-wj",
"_score": 1,
"_source": {
"comments": [
{
"user": "张三",
"content": "遛狗不拴绳,等于狗遛人"
},
{
"user": "李四",
"content": "那最后是什么处理结果呢"
},
{
"user": "王五",
"content": "大型犬太危险了"
}
],
"comments_count": 3,
"title": "女子在小区车库遇1米多高阿富汗猎犬"
}
}
]
再次执行nested搜索,结果没有召回任何文档,符合需求。
json
GET news-nested/_search
{
"profile": false,
"query": {
"bool": {
"must": [
{
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{
"match": {
"comments.user": "张三"
}
},
{
"match": {
"comments.content": "危险"
}
}
]
}
}
}
}
]
}
}
}
6. 尾巴
Elasticsearch索引是一系列相关文档的集合,文档又是一组字段的集合,每个字段有自己的数据类型,Elasticsearch可以索引并检索和分析文档数据。文档的增删改操作即可以针对单个文档进行,也可以批量处理,当面临大量操作时,使用批量API可以大幅提升性能。最后,当需要修改索引的静态配置时,可以通过reindex API在索引间迁移数据。