使用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"
  }'

成功响应包含_idresultcreated

{
  "_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会替换整个文档。这种替换行为对于简单的同步任务很有用,但也可能删除您忘记发送的字段。

例如,如果现有文档包含categorybranddescription,而您的下一个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、响应状态和项目级批量错误。您不需要永远记录每个完整文档,但在部署期间,这些字段可以节省数小时。