ElasticSearch bulk批量增删改语法(来自学习资料 + 自己整理,第27节)

本文介绍如何使用 Elasticsearch 的 Bulk API 执行批量 CRUD 操作,包括创建、更新、删除文档等,并探讨了最佳 bulk size 的选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 bulk语法

通过bulk语法,可以将crud所需的不同的操作放在一个语句里面。
先来查找一下看是否有数据:

查询命令为如下时:

GET /test_index/test_type/1

查询的结果是:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "1",
  "_version": 2,
  "found": true,
  "_source": {
    "test_field1": "test field1",
    "test_field2": "test field2"
  }
}

查询命令为如下时:

GET /test_index/test_type/2

查询结果是:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "2",
  "_version": 1,
  "found": true,
  "_source": {
    "test_content": "my test"
  }
}

说明id为2的这个数据是存在的,同时id为1的那个数据也存在。

这里写图片描述

接下来模拟,先删除,在创建等过程。使用bulk语法的特点是,当其中一个失败了之后,不影响其它的操作,其它的操作该成功还是会成功,该失败还是会失败。

注意:
1、实际的时候,要将带有#号的行去掉。
2、bulk api对json的语法,有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有换行。

POST /_bulk
#id为2的数据将会被删除
{"delete":{"_index":"test_index","_type":"test_type","_id":2}}
#id为3的将会被创建
{"create":{"_index":"test_index","_type":"test_type","_id":3}}
#表示id为3的文档的内容
{"test_field":"test3"}
#创建id为2的文档
{"create":{"_index":"test_index","_type":"test_type","_id":2}}
#表示的文档的内容为test2
{"test_field":"test2"}
#创建索引,id为4的
{"index":{"_index":"test_index","_type":"test_type","_id":4}}
#这里是内容
{"test_field":"test4"}
#其实,这里面已经存在id为1的值,下面的相当于是替换
{"index":{"_index":"test_index","_type":"test_type","_id":1}}
替换成的内容是下面的内容
{"test_field":"replaced test1111"}
#下面表示将id为1的做更新操作
{ "update": { "_index": "test_index", "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
{ "doc" : {"test_field2" : "bulk test1"} }

即:总的操作命令为:

POST /_bulk
{"delete":{"_index":"test_index","_type":"test_type","_id":"2"}}
{"create":{"_index":"test_index","_type":"test_type","_id":"3"}}
{"test_field":"test3"}
{"create":{"_index":"test_index","_type":"test_type","_id":"2"}}
{"test_field":"test2"}
{"index":{"_index":"test_index","_type":"test_type","_id":"4"}}
{"test_field":"test4"}
{"index":{"_index":"test_index","_type":"test_type","_id":"1"}}
{"test_field":"replaced test1111","test_field2":"test_field2"}
{ "update": { "_index": "test_index", "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
{ "doc" : {"test_field2" : "bulk test1"} }

如果未遵循注意点2的要求,会报如下错误:

{
  "error": {
    "root_cause": [
      {
        "type": "json_e_o_f_exception",
        "reason": "Unexpected end-of-input: expected close marker for Object (start marker at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@7c1cd2c1; line: 1, column: 1])\n at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@7c1cd2c1; line: 1, column: 3]"
      }
    ],
    "type": "json_e_o_f_exception",
    "reason": "Unexpected end-of-input: expected close marker for Object (start marker at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@7c1cd2c1; line: 1, column: 1])\n at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@7c1cd2c1; line: 1, column: 3]"
  },
  "status": 500
}

这里写图片描述

经过上面的bulk操作,依次执行下面的命令:

GET /test_index/test_type/2

结果是:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "2",
  "_version": 5,
  "found": true,
  "_source": {
    "test_field": "test2"
  }
}

执行命令:

GET /test_index/test_type/3

运行结果是:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "3",
  "_version": 1,
  "found": true,
  "_source": {
    "test_field": "test3"
  }
}

执行命令:

GET /test_index/test_type/4

查询结果是:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "4",
  "_version": 4,
  "found": true,
  "_source": {
    "test_field": "test4"
  }
}

执行命令:

{
  "_index": "test_index",
  "_type": "test_type",
  "_id": "1",
  "_version": 6,
  "found": true,
  "_source": {
    "test_field": "replaced test1111",
    "test_field2": "bulk test1"
  }
}

总结,有哪些类型的操作可以执行呢?

(1)delete:删除一个文档,只要1个json串就可以了
(2)create:PUT /index/type/id/_create,强制创建,需要两行,下一行表示所需的数据
(3)index:普通的put操作,可以是创建文档,也可以是全量替换文档
(5)update:执行的partial update操作

bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志

#2、bulk size最佳大小
bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size.
一般从1000 ~ 5000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在5~15MB之间。

### Elasticsearch 全量同步与增量同步 #### 一、概念区分 全量同步指的是将源数据库中的所有数据一次性迁移到Elasticsearch中。这种方式适用于初次构建索引或当数据量不大时的情况[^3]。 增量同步则是指只迁移自上次成功传输之后发生变化的数据条目(即新插入、更新过的记录以及被标记为已删除的项)。这种方法能够显著减少资源消耗并提高效率,尤其是在处理大规模动态变化的数据集上表现优异[^2]。 #### 二、实现方式对比 对于**全量同步**而言: - 可以利用Spring Data Elasticsearch框架完成较为复杂的数据映射关系定义,并且整个过程完全由应用程序控制而不依赖额外组件支持[^1]。 ```java // 使用 Spring Data Elasticsearch 进行全量导入的一个简单例子 @Autowired private ElasticsearchOperations operations; public void bulkImport() { List<YourEntity> entities = yourRepository.findAll(); BulkRequestBuilder bulkRequest = new BulkRequest(); for (YourEntity entity : entities) { IndexRequest indexRequest = new IndexRequest("your_index"); indexRequest.source(JSON.toJSONString(entity), XContentType.JSON); bulkRequest.add(indexRequest); } try { BulkResponse responses = bulkRequest.get(); if (!responses.hasFailures()) { System.out.println("Bulk import succeeded."); } else { System.err.println(responses.buildFailureMessage()); } } catch (Exception e) { throw new RuntimeException(e); } } ``` 而针对**增量同步**: - 阿里巴巴开发了一个名为Canal的工具来监听MySQL binlog事件流,从而捕捉到任何DDL/DML语句所引起的变化;随后这些更改会被转发至目标存储系统如Elasticsearch之中。 另一种常见的做法是在原生的关系型数据库内部设置触发器机制配合专门的日志表用于追踪变动情况,再借助于后台作业定期读取该日志并将相应改动应用到远端集群内。 ```sql CREATE TRIGGER after_insert_trigger AFTER INSERT ON `source_table` FOR EACH ROW BEGIN INSERT INTO change_log (`operation`, `table_name`, `id`) VALUES ('insert', 'source_table', NEW.id); END; ``` ```sql CREATE TRIGGER after_update_trigger AFTER UPDATE ON `source_table` FOR EACH ROW BEGIN IF NOT ((OLD.col1 <=> NEW.col1 AND OLD.col2 <=> NEW.col2)) THEN INSERT INTO change_log (`operation`, `table_name`, `id`) VALUES ('update', 'source_table', NEW.id); END IF; END; ``` ```sql CREATE TRIGGER after_delete_trigger AFTER DELETE ON `source_table` FOR EACH ROW BEGIN INSERT INTO change_log (`operation`, `table_name`, `id`) VALUES ('delete', 'source_table', OLD.id); END; ``` ```java // Java 定时任务执行变更同步逻辑片段 @Scheduled(cron="*/5 * * * * ?") // 每隔五秒运行一次 public void syncChangesToEs(){ List<Map<String, Object>> changes = getUnprocessedChangeLogs(); // 获取未处理的日志 for(Map<String,Object> logEntry : changes){ String operationType = (String)logEntry.get("operation_type"); switch(operationType.toLowerCase()){ case "insert": performInsertOperation(logEntry); break; case "update": performUpdateOperation(logEntry); break; case "delete": performDeleteOperation(logEntry); break; default: logger.warn("Unknown operation type encountered: "+operationType); } markLogAsProcessed((Integer)logEntry.get("id")); // 将此条日志状态设为已处理 } } private void performInsertOperation(Map<String, Object> entry){ YourEntity entity = convertMapToEntity(entry); IndexRequest request = new IndexRequest("target_index").id(String.valueOf(entry.get("id"))); request.source(convertObjectToJsonBytes(entity), XContentType.JSON); try{ client.index(request, RequestOptions.DEFAULT); }catch(Exception ex){ handleIndexingError(ex.getMessage(),entry); } } private void performUpdateOperation(Map<String, Object> entry){ UpdateRequest updateReq = new UpdateRequest("target_index", String.valueOf(entry.get("id"))) .doc(convertObjectToJsonBytes(entry),XContentType.JSON) .upsert(new BytesArray("{}"),XContentType.JSON); try{ client.update(updateReq,RequestOptions.DEFAULT); }catch(Exception ex){ handleErrorDuringUpdatingRecord(ex.getMessage(),entry); } } private void performDeleteOperation(Map<String, Object> entry){ DeleteRequest deleteReq = new DeleteRequest("target_index", String.valueOf(entry.get("id"))); try{ client.delete(deleteReq,RequestOptions.DEFAULT); }catch(Exception ex){ handleErrorWhileDeletingDocument(ex.getMessage(),entry); } } ``` #### 三、增删改查操作指南 无论是采用哪种同步策略,在日常维护过程中都需要掌握基本的操作技能以便及时响应业务需求: - **创建/添加文档** 可以通过发送HTTP POST请求向指定索引下新增加一份JSON格式的内容作为新的文档实例[^4]。 ```bash curl -XPOST 'http://122.51.134.139:9200/myindex/_doc' -d' { "title": "Test Document", "content": "This is a test document." }' ``` - **检索文档** 要查找特定条件下的多份或多类别的资料,则可通过GET方法携带查询字符串参数或者更高级别的DSL语法结构发起搜索命令。 ```bash curl -XGET 'http://122.51.134.139:9200/myindex/_search?q=title:test&size=10' ``` - **修改现有文档** 如果只是部分字段需要调整的话推荐使用PATCH动词加上对应的路径表达式来进行局部刷新;而对于整体替换的情形则更适合PUT动作。 ```bash # 局部更新 curl -XPATCH 'http://122.51.134.139:9200/myindex/_doc/doc_id/_update?pretty=true' -H 'Content-Type: application/json' -d ' {"script":{"inline":"ctx._source.counter += params.count","lang":"painless","params":{"count":1}}}' # 整体覆盖 curl -XPUT 'http://122.51.134.139:9200/myindex/_doc/doc_id?pretty=true' -H 'Content-Type: application/json' -d '{...}' ``` - **移除单个或批量文档** 最后就是关于清理不再需要的信息点了,这同样很简单只需要调用DELETE接口即可达成目的。 ```bash # 删除单一文件 curl -XDELETE 'http://122.51.134.139:9200/myindex/_doc/doc_id' # 批量清除满足一定规则的结果集合 curl -XPOST 'http://122.51.134.139:9200/myindex/_delete_by_query?conflicts=proceed' -H 'Content-Type: application/json' -d ' { "query": { "match_all": {} }}' ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涂作权的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值