Elasticsearch文档更新 #
一、更新概述 #
1.1 更新特点 #
text
Elasticsearch更新特点
├── 文档不可变
│ └── 更新实际上是删除旧文档,创建新文档
├── 部分更新
│ └── 只更新指定字段
├── 脚本更新
│ └── 使用脚本进行复杂更新
└── 并发控制
└── 使用版本号保证一致性
1.2 更新方式对比 #
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 索引替换 | 完整替换文档 | 全量更新 |
| 部分更新 | 更新指定字段 | 字段更新 |
| 脚本更新 | 使用脚本更新 | 复杂逻辑 |
| upsert | 存在则更新,不存在则创建 | 幂等操作 |
二、索引替换 #
2.1 完整替换 #
bash
PUT /products/_doc/1
{
"name": "iPhone 15 Pro",
"price": 1099,
"brand": "Apple",
"category": "Electronics"
}
注意:会完全替换原文档,包括未指定的字段。
2.2 检测不存在文档 #
bash
PUT /products/_doc/1?op_type=create
{
"name": "iPhone 15",
"price": 999
}
三、部分更新 #
3.1 基本部分更新 #
bash
POST /products/_update/1
{
"doc": {
"price": 899,
"in_stock": true
}
}
响应:
json
{
"_index": "products",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
3.2 更新参数 #
| 参数 | 说明 | 默认值 |
|---|---|---|
| doc | 部分文档 | - |
| doc_as_upsert | 文档不存在时作为新文档插入 | false |
| detect_noop | 检测无变化 | true |
| refresh | 是否刷新 | false |
| retry_on_conflict | 冲突重试次数 | 0 |
3.3 doc_as_upsert #
bash
POST /products/_update/1
{
"doc": {
"name": "iPhone 15",
"price": 999
},
"doc_as_upsert": true
}
3.4 detect_noop #
bash
POST /products/_update/1
{
"doc": {
"price": 999
},
"detect_noop": false
}
当 detect_noop: true 时,如果更新内容与原文档相同,则不执行更新操作。
四、脚本更新 #
4.1 内联脚本 #
bash
POST /products/_update/1
{
"script": {
"source": "ctx._source.price += params.increment",
"params": {
"increment": 100
}
}
}
4.2 更新多个字段 #
bash
POST /products/_update/1
{
"script": {
"source": """
ctx._source.price = params.newPrice;
ctx._source.updated_at = params.updatedAt;
""",
"params": {
"newPrice": 899,
"updatedAt": "2024-01-01"
}
}
}
4.3 条件更新 #
bash
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.price > params.maxPrice) {
ctx._source.price = params.maxPrice;
}
""",
"params": {
"maxPrice": 1000
}
}
}
4.4 删除字段 #
bash
POST /products/_update/1
{
"script": {
"source": "ctx._source.remove('field_to_delete')"
}
}
4.5 添加数组元素 #
bash
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.tags == null) {
ctx._source.tags = new ArrayList();
}
ctx._source.tags.add(params.newTag);
""",
"params": {
"newTag": "featured"
}
}
}
4.6 删除数组元素 #
bash
POST /products/_update/1
{
"script": {
"source": "ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag))",
"params": {
"tag": "featured"
}
}
}
4.7 存储脚本 #
bash
PUT /_scripts/update_price
{
"script": {
"lang": "painless",
"source": "ctx._source.price += params.increment"
}
}
使用存储脚本:
bash
POST /products/_update/1
{
"script": {
"id": "update_price",
"params": {
"increment": 100
}
}
}
五、Upsert操作 #
5.1 基本upsert #
bash
POST /products/_update/1
{
"script": {
"source": "ctx._source.views += 1"
},
"upsert": {
"name": "iPhone 15",
"views": 1
}
}
5.2 scripted_upsert #
bash
POST /products/_update/1
{
"script": {
"source": """
if (ctx.op == 'create') {
ctx._source.views = 1;
} else {
ctx._source.views += 1;
}
""",
"params": {}
},
"scripted_upsert": true
}
六、并发控制 #
6.1 乐观并发控制 #
bash
GET /products/_doc/1
PUT /products/_doc/1?if_seq_no=0&if_primary_term=1
{
"name": "iPhone 15",
"price": 899
}
6.2 重试冲突 #
bash
POST /products/_update/1?retry_on_conflict=3
{
"script": {
"source": "ctx._source.views += 1"
}
}
6.3 版本控制 #
bash
POST /products/_update/1?version=2&version_type=external
{
"doc": {
"price": 899
}
}
七、批量更新 #
7.1 使用Bulk API #
bash
POST /_bulk
{"update": {"_index": "products", "_id": "1"}}
{"doc": {"price": 899}}
{"update": {"_index": "products", "_id": "2"}}
{"doc": {"price": 799}}
{"update": {"_index": "products", "_id": "3"}}
{"doc": {"price": 699}}
7.2 批量脚本更新 #
bash
POST /_bulk
{"update": {"_index": "products", "_id": "1"}}
{"script": {"source": "ctx._source.price += 100"}}
{"update": {"_index": "products", "_id": "2"}}
{"script": {"source": "ctx._source.price += 100"}}
八、Update By Query #
8.1 基本用法 #
bash
POST /products/_update_by_query
{
"query": {
"term": {
"brand": "Apple"
}
},
"script": {
"source": "ctx._source.discount = 0.1"
}
}
响应:
json
{
"took": 150,
"timed_out": false,
"total": 100,
"updated": 100,
"deleted": 0,
"batches": 1,
"version_conflicts": 0,
"failures": []
}
8.2 更新所有文档 #
bash
POST /products/_update_by_query
{
"query": {
"match_all": {}
},
"script": {
"source": "ctx._source.updated_at = params.now",
"params": {
"now": "2024-01-01"
}
}
}
8.3 条件更新 #
bash
POST /products/_update_by_query
{
"query": {
"range": {
"price": {
"gte": 1000
}
}
},
"script": {
"source": "ctx._source.category = 'premium'"
}
}
8.4 异步执行 #
bash
POST /products/_update_by_query?wait_for_completion=false
{
"query": {
"match_all": {}
},
"script": {
"source": "ctx._source.processed = true"
}
}
响应:
json
{
"task": "node_id:12345"
}
查看任务状态:
bash
GET /_tasks/node_id:12345
8.5 取消任务 #
bash
POST /_tasks/node_id:12345/_cancel
8.6 切片并行 #
bash
POST /products/_update_by_query?slices=5
{
"query": {
"match_all": {}
},
"script": {
"source": "ctx._source.processed = true"
}
}
九、脚本详解 #
9.1 Painless脚本语法 #
javascript
// 变量声明
def x = 1;
def y = 'hello';
// 条件语句
if (ctx._source.price > 100) {
ctx._source.category = 'premium';
} else {
ctx._source.category = 'standard';
}
// 循环
for (def tag : ctx._source.tags) {
if (tag == 'featured') {
ctx._source.featured = true;
}
}
// 操作文档
ctx._source.newField = 'value';
ctx._source.remove('oldField');
9.2 常用脚本示例 #
数值计算:
bash
POST /products/_update/1
{
"script": {
"source": """
ctx._source.price = ctx._source.price * (1 - params.discount);
ctx._source.discount_applied = true;
""",
"params": {
"discount": 0.1
}
}
}
日期处理:
bash
POST /products/_update/1
{
"script": {
"source": """
ctx._source.updated_at = new SimpleDateFormat('yyyy-MM-dd').format(new Date());
"""
}
}
数组操作:
bash
POST /products/_update/1
{
"script": {
"source": """
if (!ctx._source.tags.contains(params.tag)) {
ctx._source.tags.add(params.tag);
}
""",
"params": {
"tag": "new"
}
}
}
9.3 条件删除 #
bash
POST /products/_update/1
{
"script": {
"source": """
if (ctx._source.status == 'deleted') {
ctx.op = 'delete';
} else {
ctx.op = 'none';
}
"""
}
}
十、更新性能优化 #
10.1 批量更新优化 #
text
优化建议
├── 使用Bulk API
│ └── 减少网络开销
├── 合理批量大小
│ └── 5-15MB
├── 禁用refresh
│ └── 批量更新时
└── 使用脚本
└── 减少文档读取
10.2 脚本性能 #
text
脚本优化
├── 使用存储脚本
│ └── 避免每次编译
├── 使用params
│ └── 参数化脚本
├── 避免复杂逻辑
│ └── 简化脚本
└── 合理使用缓存
└── 脚本缓存
十一、更新错误处理 #
11.1 常见错误 #
| 错误 | 原因 | 解决方案 |
|---|---|---|
| version_conflict_engine_exception | 版本冲突 | 使用retry_on_conflict |
| document_missing_exception | 文档不存在 | 使用upsert |
| script_exception | 脚本错误 | 检查脚本语法 |
| illegal_argument_exception | 参数错误 | 检查参数 |
11.2 错误处理示例 #
bash
POST /products/_update/1?retry_on_conflict=5
{
"script": {
"source": "ctx._source.views += 1"
},
"upsert": {
"views": 1
}
}
十二、最佳实践 #
12.1 更新策略选择 #
text
更新策略
├── 简单字段更新
│ └── 使用doc部分更新
├── 复杂逻辑更新
│ └── 使用脚本更新
├── 批量更新
│ └── 使用Update By Query
└── 幂等操作
└── 使用upsert
12.2 并发控制建议 #
text
并发控制
├── 高并发场景
│ └── 使用retry_on_conflict
├── 需要精确控制
│ └── 使用seq_no和primary_term
└── 外部版本控制
└── 使用external版本类型
十三、总结 #
本章介绍了Elasticsearch文档更新:
- 部分更新只更新指定字段
- 脚本更新支持复杂逻辑
- upsert实现幂等操作
- 并发控制保证数据一致性
- Update By Query支持批量更新
- 合理使用参数优化性能
下一步,我们将学习文档删除操作。
最后更新:2026-03-27