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文档更新:

  1. 部分更新只更新指定字段
  2. 脚本更新支持复杂逻辑
  3. upsert实现幂等操作
  4. 并发控制保证数据一致性
  5. Update By Query支持批量更新
  6. 合理使用参数优化性能

下一步,我们将学习文档删除操作。

最后更新:2026-03-27