使用Elasticsearch REST API索引和更新文档
掌握Elasticsearch中使用REST API的核心创建、读取、更新、删除(CRUD)操作。本指南详细说明了索引新文档(带或不带指定ID)以及执行现有记录的细粒度部分更新所需的精确HTTP请求、端点和JSON负载。学习实用的curl示例,包括原子更新、脚本化修改和高效批量数据摄取。
使用Elasticsearch REST API索引和更新文档
使用Elasticsearch REST API索引和更新文档起初看起来很简单:向端点发送JSON并接收JSON。但在生产环境中,关键部分更为具体。您是在创建新文档还是替换旧文档?您希望Elasticsearch生成ID,还是需要来自源系统的ID?您是在进行部分更新,还是意外覆盖了本应保留的字段?
以下示例使用localhost:9200、名为products的索引和纯curl。在实际集群中,您可能还需要HTTPS、身份验证和证书选项。
curl -u elastic:password --cacert http_ca.crt https://es.example.com:9200/
使用自动ID创建文档
当Elasticsearch可以分配文档ID时,使用POST /{index}/_doc。
curl -X POST "localhost:9200/products/_doc" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Wireless Mouse X1",
"price": 25.99,
"in_stock": true,
"updated_at": "2026-05-24T09:30:00Z"
}'
成功响应包含_id和result为created。
{
"_index": "products",
"_id": "wI4aQZkB8xExample",
"_version": 1,
"result": "created"
}
自动ID适用于事件数据、日志、点击记录和仅追加内容。当同一真实世界项目可能稍后再次出现时,自动ID并不理想。如果相同的产品更新使用自动ID被索引两次,您将得到两个文档。
使用自定义ID索引
当您的源系统已有稳定标识符时,使用PUT /{index}/_doc/{id}。
curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Wireless Mouse X1",
"price": 25.99,
"in_stock": true,
"updated_at": "2026-05-24T09:30:00Z"
}'
如果ID不存在,Elasticsearch会创建它。如果已存在,Elasticsearch会替换整个文档。这种替换行为对于简单的同步任务很有用,但也可能删除您忘记发送的字段。
例如,如果现有文档包含category、brand和description,而您的下一个PUT只发送price,则存储的_source将仅包含该新请求中的字段。当您发送完整的所需文档时,使用PUT。
仅在缺失时创建
如果重复创建是错误,请使用创建操作:
curl -X PUT "localhost:9200/products/_create/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Wireless Mouse X1",
"price": 25.99
}'
如果ID已存在,Elasticsearch返回版本冲突而不是覆盖文档。这对于摄取作业很有用,重试消息不应更改现有记录。
部分更新字段
当您只想更改某些字段时,使用POST /{index}/_update/{id}并带doc对象。
curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"doc": {
"price": 22.49,
"in_stock": false,
"updated_at": "2026-05-24T10:15:00Z"
}
}'
Elasticsearch内部获取现有文档,合并提供的字段,并再次索引更改后的源。从API用户的角度来看,这是部分更新,而不是磁盘上的原地修改。
如果文档可能尚不存在,请使用doc_as_upsert:
curl -X POST "localhost:9200/products/_update/sku-keyboard-k90" \
-H "Content-Type: application/json" \
-d '{
"doc": {
"sku": "keyboard-k90",
"name": "Mechanical Keyboard K90",
"price": 129.99,
"in_stock": true
},
"doc_as_upsert": true
}'
如果文档缺失,这将使用doc内容创建文档。当您的上游源发送“当前状态”记录,并且您不关心这是否是Elasticsearch第一次看到该ID时,请使用此方法。
脚本化更新
当新值依赖于旧值时,脚本很有用。一个常见示例是递增计数器:
curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"script": {
"source": "ctx._source.views = (ctx._source.views ?: 0) + params.count",
"params": {
"count": 1
}
}
}'
保持脚本小而可预测。它们功能强大,但也比普通的doc更新更容易被误用。对于高流量计数器,请仔细考虑写入速率、刷新需求以及Elasticsearch是否应作为记录系统。
批量索引和更新
对于超过几个文档的情况,使用_bulk。请求体是换行分隔的JSON,并且最后的换行符很重要。
cat bulk-products.ndjson
{"index":{"_index":"products","_id":"sku-mouse-x1"}}
{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99,"in_stock":true}
{"update":{"_index":"products","_id":"sku-keyboard-k90"}}
{"doc":{"price":119.99,"in_stock":true},"doc_as_upsert":true}
{"delete":{"_index":"products","_id":"sku-old-cable"}}
像这样发送:
curl -X POST "localhost:9200/_bulk" \
-H "Content-Type: application/x-ndjson" \
--data-binary @bulk-products.ndjson
使用--data-binary,而不是普通的-d,以便curl保留换行符。然后检查响应。批量请求可能返回HTTP 200,而个别项目失败。
curl -s -X POST "localhost:9200/_bulk" \
-H "Content-Type: application/x-ndjson" \
--data-binary @bulk-products.ndjson | jq '.errors, .items[] | select(.index.error or .update.error or .delete.error)'
刷新和可见性
索引的文档并不总是立即可搜索。默认情况下,Elasticsearch会定期刷新索引。如果您需要通过ID读取文档,GET /products/_doc/sku-mouse-x1可以立即找到它。如果您需要在继续测试前使其出现在搜索结果中,请使用refresh=wait_for:
curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1?refresh=wait_for" \
-H "Content-Type: application/json" \
-d '{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99}'
不要在没有衡量成本的情况下,在每个生产写入中添加强制刷新。它们可能会降低索引吞吐量。
实用经验法则
对于仅追加数据,使用POST /_doc,其中重复事件可接受或在其他地方处理。当请求包含完整的当前文档时,使用PUT /_doc/{id}。当您只想更改某些字段时,使用_update。当覆盖会隐藏数据问题时,使用_create。当工作量超过几个文档时,使用_bulk。
大多数Elasticsearch索引错误源于选择了错误的写入形状。端点应与您的源数据、重试行为以及Elasticsearch是存储完整记录还是其他系统记录的可搜索副本相匹配。
在归咎于写入请求前检查映射
有时写入成功,但搜索结果仍然看起来不对。这通常是映射问题,而不是索引端点问题。
检查映射:
curl -s "localhost:9200/products/_mapping?pretty"
如果price最初因为早期测试文档发送了字符串"25.99"而被索引为文本,范围查询可能会表现不佳。如果sku需要精确匹配和聚合,它通常应该是keyword字段或具有keyword子字段。如果时间戳以不同格式到达,某些文档可能在索引时被拒绝,而其他文档成功。
对于可预测的系统,在第一次写入前创建索引映射:
curl -X PUT "localhost:9200/products" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"sku": { "type": "keyword" },
"name": { "type": "text" },
"price": { "type": "double" },
"in_stock": { "type": "boolean" },
"updated_at": { "type": "date" }
}
}
}'
动态映射便于探索。对于生产摄取,显式映射可防止错误的第一个文档塑造索引。
有意识地处理冲突和重试
并发更新可能冲突。如果两个工作进程几乎同时更新同一文档,其中一个可能基于旧版本。对于简单的更新重试,Elasticsearch支持retry_on_conflict:
curl -X POST "localhost:9200/products/_update/sku-mouse-x1?retry_on_conflict=3" \
-H "Content-Type: application/json" \
-d '{
"doc": {
"price": 21.99
}
}'
这对于低风险更新很有用,但不能替代明确的所有权。如果一个系统拥有产品名称,另一个拥有库存,请考虑通过定义良好的管道更新不同字段。如果事件顺序重要,请包含源时间戳,并在您的摄取层或通过仔细的脚本拒绝较旧的更新。
调试时先读后写
测试索引流程时,立即通过ID获取文档:
curl -s "localhost:9200/products/_doc/sku-mouse-x1?pretty"
然后搜索它:
curl -s "localhost:9200/products/_search?pretty" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"sku": "mouse-x1"
}
}
}'
如果通过ID的GET有效但搜索无效,请考虑刷新、映射、分析器或查询类型。如果两者都失败,请考虑索引错误、错误索引、错误ID、身份验证或路由。
对于应用程序,记录目标索引、文档ID、响应状态和项目级批量错误。您不需要永远记录每个完整文档,但在部署期间,这些字段可以节省数小时。