Elasticsearch 学习笔记 - 文档操作完整篇
摘要 :本文全面系统地介绍了 Elasticsearch 文档操作的核心知识,涵盖文档的新增 (指定ID、自动生成ID、创建式新增)、更新 (全量更新、部分更新、脚本更新、批量更新)、查询 (ID查询、字段过滤、批量查询)和删除 (ID删除、批量删除、按查询删除)四大操作。文章不仅提供了详细的语法示例和返回结果解析,还通过实际应用场景 (用户管理、商品管理、日志记录、订单管理、计数器)展示了如何在实际项目中应用这些操作。最后,总结了最佳实践建议 、常用命令速查 、返回结果字段详解 和注意事项,帮助开发者高效、安全地使用 Elasticsearch 进行文档操作。
目录
一、文档新增操作
Elasticsearch提供了多种文档新增方式,根据不同的业务场景可以选择合适的方法。
1.1 指定ID新增文档
语法格式
PUT /<index>/_doc/<_id>
POST /<index>/_doc/<_id>
示例
PUT /user/_doc/1
{
"name": "张三",
"age": 20,
"gender": "男"
}
返回结果
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
特点
- 使用PUT或POST方法均可
- 需要手动指定文档ID
- 如果ID已存在则会覆盖原文档(版本号会增加)
- 如果ID不存在则创建新文档
1.2 自动生成ID新增文档
语法格式
POST /<index>/_doc
示例
POST /user/_doc
{
"name": "李四",
"age": 25,
"gender": "女"
}
返回结果
{
"_index" : "user",
"_type" : "_doc",
"_id" : "Ax1C123456789abc",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
特点
- 只能使用POST方法
- Elasticsearch自动生成唯一ID(格式:类似UUID)
- 避免ID冲突,适合分布式环境
- 返回结果中包含生成的文档ID
1.3 创建式新增(避免覆盖)
语法格式
PUT /<index>/_create/<_id>
示例
PUT /user/_create/1
{
"name": "王五",
"age": 30,
"gender": "男"
}
特点
- 使用
_create端点
- 只能用于新增,不能用于更新
- 如果ID已存在会报错(不会覆盖)
ID已存在时的错误返回
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict",
"index_uuid": "test_index",
"shard": "0",
"index": "user"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict",
"index_uuid": "test_index",
"shard": "0",
"index": "user"
},
"status": 409
}
1.4 新增文档方式对比
| 方式 |
端点 |
ID存在时的行为 |
result字段 |
适用场景 |
| PUT + _doc |
_doc |
覆盖更新 |
created/updated |
通用场景,需要更新功能 |
| POST + _doc(指定ID) |
_doc |
覆盖更新 |
created/updated |
通用场景 |
| POST + _doc(无ID) |
_doc |
创建新文档 |
created |
批量导入,无主键数据 |
| PUT + _create |
_create |
报错(409) |
created |
确保不覆盖,强制新增 |
二、文档更新操作
Elasticsearch提供了多种文档更新方式,从全量覆盖到部分字段更新,再到复杂的脚本更新。
2.1 全量更新文档
2.1.1 使用 _doc 端点全量更新
语法格式
PUT /<index>/_doc/<_id>
POST /<index>/_doc/<_id>
示例
PUT /user/_doc/1
{
"name": "张三",
"age": 21,
"gender": "男",
"city": "北京"
}
返回结果(文档存在时)
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
返回结果(文档不存在时)
{
"_index" : "user",
"_type" : "_doc",
"_id" : "999",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 5,
"_primary_term" : 1
}
特点
- 全量覆盖:会替换整个文档的_source内容
- 文档不存在时创建:ID不存在时会创建新文档
- 所有字段必须提供:未提供的字段会丢失
注意事项
⚠️ 使用这种方式更新时,必须提供完整的文档数据。如果只提供部分字段,其他字段会丢失!
// 原文档
{
"name": "张三",
"age": 20,
"gender": "男"
}
// 只更新age
PUT /user/_doc/1
{
"age": 21
}
// 结果:只保留了age字段,其他字段丢失!
{
"age": 21
}
2.2 部分更新文档
2.2.1 使用 _update 端点进行部分更新
语法格式
POST /<index>/_update/<_id>
示例(使用doc更新字段)
POST /user/_update/1
{
"doc": {
"age": 22
}
}
返回结果
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1,
"get" : {
"found" : true,
"_source" : {
"name" : "张三",
"age" : 22,
"gender" : "男"
}
}
}
特点
- 部分更新:只更新指定的字段,其他字段保持不变
- 文档必须存在:文档不存在时会返回错误
- 返回更新后的文档 :
get字段包含更新后的文档内容
文档不存在时的错误返回
{
"error": {
"root_cause": [
{
"type": "document_missing_exception",
"reason": "[user][1] missing",
"index_uuid": "test_index",
"shard": "0",
"index": "user"
}
],
"type": "document_missing_exception",
"reason": "[user][1] missing",
"index_uuid": "test_index",
"shard": "0",
"index": "user"
},
"status": 404
}
无实际变更时的返回
POST /user/_update/1
{
"doc": {
"age": 22 // age已经是22,无实际变更
}
}
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 3,
"result" : "noop",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1,
"get" : {
"found" : true,
"_source" : {
"name" : "张三",
"age" : 22,
"gender" : "男"
}
}
}
特点
result字段返回"noop"(无操作)
- 版本号不会增加
- 分片操作仍然执行
2.2.2 使用 doc_as_upsert 实现存在性检查更新
语法格式
POST /<index>/_update/<_id>
{
"doc": { ... },
"doc_as_upsert": true
}
示例(文档存在时更新,不存在时创建)
POST /user/_update/999
{
"doc": {
"name": "赵六",
"age": 28,
"gender": "男"
},
"doc_as_upsert": true
}
返回结果(文档不存在时创建)
{
"_index" : "user",
"_type" : "_doc",
"_id" : "999",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 3,
"_primary_term" : 1,
"get" : {
"found" : true,
"_source" : {
"name" : "赵六",
"age" : 28,
"gender" : "男"
}
}
}
特点
- 文档存在时:执行更新操作
- 文档不存在时:使用doc内容创建新文档
- 类似于"先查询,存在则更新,不存在则插入"的逻辑
2.2.3 使用 upsert 字段实现不同的创建逻辑
语法格式
POST /<index>/_update/<_id>
{
"doc": { ... },
"upsert": { ... }
}
示例(文档存在时执行doc更新,不存在时执行upsert创建)
POST /user/_update/1000
{
"doc": {
"last_login": "2024-08-20T10:30:00Z",
"login_count": 5
},
"upsert": {
"username": "new_user",
"created_at": "2024-08-20T10:30:00Z",
"last_login": "2024-08-20T10:30:00Z",
"login_count": 1
}
}
特点
- 文档存在时 :执行
doc部分的更新
- 文档不存在时 :执行
upsert部分的内容创建
- 适用场景:更新和创建需要不同字段的情况
doc_as_upsert vs upsert 对比
| 特性 |
doc_as_upsert |
upsert |
| 文档存在时 |
执行doc更新 |
执行doc更新 |
| 文档不存在时 |
使用doc内容创建 |
使用upsert内容创建 |
| 灵活性 |
较低 |
较高 |
| 适用场景 |
更新和创建内容相同 |
更新和创建需要不同字段 |
2.3 使用脚本进行复杂更新
2.3.1 基础脚本更新
语法格式
POST /<index>/_update/<_id>
{
"script": {
"source": "...",
"lang": "painless"
}
}
示例1:增加年龄
POST /user/_update/1
{
"script": {
"source": "ctx._source.age += params.amount",
"lang": "painless",
"params": {
"amount": 1
}
}
}
示例2:添加字段
POST /user/_update/1
{
"script": {
"source": "ctx._source.city = params.city",
"lang": "painless",
"params": {
"city": "上海"
}
}
}
示例3:条件更新
POST /user/_update/1
{
"script": {
"source": "if (ctx._source.age > 18) { ctx._source.is_adult = true }",
"lang": "painless"
}
}
特点
- 支持复杂的更新逻辑
- 可以进行数学运算
- 可以添加、修改、删除字段
- 可以使用参数传递值
2.3.2 脚本更新的上下文变量
| 变量 |
说明 |
示例 |
ctx._source |
文档的_source内容 |
ctx._source.age |
ctx.op |
操作类型 |
"index" / "delete" / "none" |
params |
传递给脚本的参数 |
params.amount |
示例:使用ctx.op控制操作
POST /user/_update/1
{
"script": {
"source": "if (ctx._source.age < 0) { ctx.op = 'delete' } else { ctx._source.age += params.amount }",
"lang": "painless",
"params": {
"amount": 1
}
}
}
2.3.3 脚本upsert
语法格式
POST /<index>/_update/<_id>
{
"script": {
"source": "...",
"lang": "painless"
},
"upsert": { ... }
}
示例
POST /user/_update/1001
{
"script": {
"source": "ctx._source.login_count++",
"lang": "painless"
},
"upsert": {
"username": "user1001",
"login_count": 1,
"created_at": "2024-08-20T10:30:00Z"
}
}
特点
- 文档存在时:执行脚本更新
- 文档不存在时:使用upsert内容创建
- 适用于计数器、累加器等场景
2.3.4 脚本upsert with scripted_upsert
语法格式
POST /<index>/_update/<_id>
{
"script": {
"source": "...",
"lang": "painless",
"scripted_upsert": true
},
"upsert": { ... }
}
示例
POST /counter/_update/1
{
"script": {
"source": """
if (ctx.op == 'none') {
ctx._source.count = 1;
} else {
ctx._source.count++;
}
""",
"lang": "painless",
"scripted_upsert": true
},
"upsert": {}
}
特点
- 文档不存在时也会执行脚本
ctx.op == 'none'表示文档不存在
- 更灵活的控制逻辑
2.4 检测noop(无操作)
2.4.1 使用 detect_noop 参数
语法格式
POST /<index>/_update/<_id>?detect_noop=true
默认行为
detect_noop默认为true
- 当更新内容与现有内容相同时,返回
noop
禁用noop检测
POST /user/_update/1?detect_noop=false
{
"doc": {
"age": 22
}
}
特点
- 即使值相同,也会执行更新
- 版本号会增加
result返回updated而非noop
2.5 批量更新操作
2.5.1 使用 _bulk API 批量更新
语法格式
POST /_bulk
示例
POST /_bulk
{ "update" : { "_index" : "user", "_id" : "1" } }
{ "doc" : { "age" : 23 } }
{ "update" : { "_index" : "user", "_id" : "2" } }
{ "doc" : { "city" : "深圳" } }
{ "update" : { "_index" : "user", "_id" : "3" } }
{ "doc" : { "status" : "active" } }
返回结果
{
"took": 15,
"errors": false,
"items": [
{
"update": {
"_index": "user",
"_type": "_doc",
"_id": "1",
"_version": 4,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1,
"status": 200
}
},
{
"update": {
"_index": "user",
"_type": "_doc",
"_id": "2",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1,
"status": 200
}
},
{
"update": {
"_index": "user",
"_type": "_doc",
"_id": "3",
"_version": 2,
"result": "noop",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 5,
"_primary_term": 1,
"status": 200
}
}
]
}
特点
- 一次请求更新多个文档
- 返回每个更新的结果
- 通过
errors字段判断是否有操作失败
2.5.2 批量更新时的错误处理
示例(部分失败)
POST /_bulk
{ "update" : { "_index" : "user", "_id" : "1" } }
{ "doc" : { "age" : 23 } }
{ "update" : { "_index" : "user", "_id" : "999" } }
{ "doc" : { "city" : "广州" } }
返回结果(文档不存在)
{
"took": 10,
"errors": true,
"items": [
{
"update": {
"_index": "user",
"_type": "_doc",
"_id": "1",
"_version": 4,
"result": "updated",
"status": 200
}
},
{
"update": {
"_index": "user",
"_type": "_doc",
"_id": "999",
"error": {
"type": "document_missing_exception",
"reason": "[user][999] missing",
"index_uuid": "test_index",
"shard": "0",
"index": "user"
},
"status": 404
}
}
]
}
2.6 按查询更新(Update By Query)
语法格式
POST /<index>/_update_by_query
示例
POST /user/_update_by_query
{
"query": {
"match": {
"city": "北京"
}
},
"script": {
"source": "ctx._source.region = '华北'",
"lang": "painless"
}
}
返回结果
{
"took": 45,
"timed_out": false,
"total": 100,
"updated": 100,
"deleted": 0,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
特点
- 根据查询条件批量更新文档
- 适用于更新符合特定条件的所有文档
- 可能会花费较长时间,取决于匹配的文档数量
2.7 更新操作对比总结
| 更新方式 |
端点 |
文档存在时 |
文档不存在时 |
适用场景 |
| 全量更新 |
_doc |
覆盖更新 |
创建新文档 |
需要替换整个文档 |
| 部分更新 |
_update + doc |
更新指定字段 |
报错(404) |
只更新部分字段 |
| doc_as_upsert |
_update + doc + doc_as_upsert |
更新指定字段 |
创建文档(使用doc内容) |
更新和创建内容相同 |
| upsert |
_update + doc + upsert |
执行doc更新 |
创建文档(使用upsert内容) |
更新和创建需要不同字段 |
| 脚本更新 |
_update + script |
执行脚本 |
报错(404) |
复杂更新逻辑 |
| 脚本upsert |
_update + script + upsert |
执行脚本 |
创建文档(使用upsert内容) |
计数器等场景 |
| 按查询更新 |
_update_by_query |
批量更新 |
- |
批量更新符合条件的文档 |
三、文档查询操作
3.1 根据ID查询文档
3.1.1 查询完整文档(包含元数据)
语法格式
GET /<index>/_doc/<_id>
示例
GET /user/_doc/1
返回结果(文档存在时)
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "张三",
"age" : 20,
"gender" : "男"
}
}
返回结果(文档不存在时)
{
"_index" : "user",
"_type" : "_doc",
"_id" : "999",
"found" : false
}
3.1.2 仅查询文档数据(不含元数据)
语法格式
GET /<index>/_source/<_id>
示例
GET /user/_source/1
返回结果(文档存在时)
{
"name" : "张三",
"age" : 20,
"gender" : "男"
}
返回结果(文档不存在时)
{
"error": {
"root_cause": [
{
"type": "index_not_found_exception",
"reason": "no such index [user]",
"index_uuid": "_na_",
"index": "user"
}
],
"type": "index_not_found_exception",
"reason": "no such index [user]",
"index_uuid": "_na_",
"index": "user"
},
"status": 404
}
3.1.3 HEAD请求检查文档是否存在
检查完整文档是否存在
HEAD /user/_doc/1
检查文档数据是否存在
HEAD /user/_source/1
返回结果
- 文档存在时:HTTP 200 OK
- 文档不存在时:HTTP 404 Not Found
特点
- HEAD请求只返回文档是否存在
- 不返回具体内容,节省网络流量
- 适用于存在性检查场景
3.2 查询字段过滤
3.2.1 仅返回指定字段(_source_includes)
语法格式
GET /<index>/_source/<_id>?_source_includes=<field1>,<field2>,...
GET /<index>/_doc/<_id>?_source_includes=<field1>,<field2>,...
示例
GET /user/_source/1?_source_includes=name,age
返回结果
{
"name" : "张三",
"age" : 20
}
3.2.2 排除指定字段(_source_excludes)
语法格式
GET /<index>/_source/<_id>?_source_excludes=<field1>,<field2>,...
GET /<index>/_doc/<_id>?_source_excludes=<field1>,<field2>,...
示例
GET /user/_source/1?_source_excludes=gender
返回结果
{
"name" : "张三",
"age" : 20
}
3.2.3 同时使用includes和excludes
示例
GET /user/_source/1?_source_includes=name,age,gender&_source_excludes=gender
注意事项
- 当同时使用时,excludes优先级更高
- 上述示例实际只返回name和age
3.3 批量查询文档
3.3.1 _msearch 端点(多搜索API)
语法格式
GET /<index>/_msearch
POST /<index>/_msearch
示例
GET /user/_msearch
{ "query": { "match_all": {} } }
{ "query": { "match": { "name": "张三" } } }
特点
- 批量执行多个搜索查询
- 返回结果数组,每个查询对应一个结果
- 提高查询效率,减少网络往返
3.4 查询操作对比总结
| 查询方式 |
端点 |
返回内容 |
found字段 |
适用场景 |
| GET |
_doc |
完整文档+元数据 |
有 |
需要版本信息、检查存在性 |
| GET |
_source |
仅文档数据 |
无 |
仅需要数据内容时 |
| HEAD |
_doc |
仅HTTP状态码 |
无 |
快速检查文档是否存在 |
| HEAD |
_source |
仅HTTP状态码 |
无 |
快速检查文档数据是否存在 |
| GET |
_msearch |
批量查询结果 |
有 |
批量查询场景 |
3.5 查询操作性能对比
| 操作 |
数据传输量 |
响应速度 |
推荐场景 |
| GET /_doc/id |
大(含元数据) |
较快 |
需要完整信息 |
| GET /_source/id |
小(仅数据) |
快 |
仅需数据 |
| HEAD /_doc/id |
极小(仅状态) |
最快 |
存在性检查 |
四、文档删除操作
4.1 根据ID删除文档
语法格式
DELETE /<index>/_doc/<_id>
示例
DELETE /user/_doc/1
4.2 删除操作返回结果
4.2.1 删除已存在的文档
返回结果
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
特点
result字段返回 "deleted"
_version 版本号会增加
- HTTP状态码:200 OK
- 文档被标记为删除,稍后会被物理清理
4.2.2 删除不存在的文档
返回结果
{
"_index" : "user",
"_type" : "_doc",
"_id" : "999",
"_version" : 1,
"result" : "not_found",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
特点
result字段返回 "not_found"
_version 版本号仍然会增加
- HTTP状态码:仍然是200 OK(而非404)
- 不会报错,操作被视为成功
4.3 删除操作的详细说明
4.3.1 删除机制
- 逻辑删除:文档首先被标记为已删除
- 版本递增:无论文档是否存在,版本号都会递增
- 物理删除:在段合并时才真正从磁盘移除
4.3.2 删除操作的result字段
| result值 |
含义 |
适用场景 |
deleted |
文档存在且已被删除 |
成功删除已存在的文档 |
not_found |
文档不存在 |
尝试删除不存在的文档 |
4.3.3 删除操作的HTTP状态码
| 状态码 |
含义 |
出现场景 |
| 200 OK |
删除请求成功 |
无论文档是否存在,只要请求格式正确 |
| 404 Not Found |
索引不存在 |
当索引本身不存在时 |
4.4 删除操作的注意事项
| 注意事项 |
说明 |
| 版本号递增 |
无论文档是否存在,删除操作都会增加版本号 |
| 延迟删除 |
删除的文档不会立即从磁盘移除,等待段合并 |
| result字段 |
deleted表示成功删除,not_found表示文档不存在 |
| HTTP状态码 |
无论文档是否存在,都返回200 OK |
| 幂等性 |
多次删除同一个文档,最终结果一致 |
4.5 批量删除操作
语法格式
POST /_bulk
示例
POST /_bulk
{ "delete" : { "_index" : "user", "_id" : "1" } }
{ "delete" : { "_index" : "user", "_id" : "2" } }
{ "delete" : { "_index" : "user", "_id" : "3" } }
特点
- 一次请求删除多个文档
- 返回每个删除操作的结果
- 即使部分文档不存在,整个操作仍返回200 OK
- 通过
errors字段判断是否有操作失败
4.6 按查询删除(Delete By Query)
语法格式
POST /<index>/_delete_by_query
示例
POST /user/_delete_by_query
{
"query": {
"match": {
"status": "inactive"
}
}
}
返回结果
{
"took": 30,
"timed_out": false,
"total": 100,
"deleted": 100,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
特点
- 根据查询条件批量删除文档
- 适用于删除符合特定条件的所有文档
- 可能会花费较长时间,取决于匹配的文档数量
五、实际应用场景示例
5.1 用户信息管理完整流程
场景说明
管理用户账户的完整生命周期,包括创建、查询、更新和删除。
操作示例
1. 新增用户
PUT /user/_doc/1001
{
"user_id": 1001,
"username": "admin",
"email": "admin@example.com",
"role": "administrator",
"created_at": "2024-01-01T00:00:00",
"status": "active"
}
2. 查询完整用户信息(含元数据)
GET /user/_doc/1001
3. 仅获取用户数据
GET /user/_source/1001
4. 仅获取用户名和邮箱
GET /user/_source/1001?_source_includes=username,email
5. 更新用户邮箱(部分更新)
POST /user/_update/1001
{
"doc": {
"email": "newemail@example.com"
}
}
6. 更新登录次数(脚本更新)
POST /user/_update/1001
{
"script": {
"source": "ctx._source.login_count++",
"lang": "painless"
}
}
7. 检查用户是否存在
HEAD /user/_doc/1001
# 返回:200 OK(存在)或 404 Not Found(不存在)
8. 删除用户
DELETE /user/_doc/1001
5.2 商品信息管理
场景说明
电商系统中的商品信息录入、更新和查询。
操作示例
1. 录入商品(自动生成ID)
POST /product/_doc
{
"product_name": "无线蓝牙耳机",
"category": "电子产品",
"price": 299.00,
"stock": 100,
"tags": ["蓝牙", "无线", "耳机"],
"description": "高音质蓝牙5.0耳机",
"created_at": "2024-08-20T10:30:00"
}
2. 查询商品(使用返回的ID)
GET /product/_doc/Ax7C123456789abc
3. 仅获取价格和库存(用于显示)
GET /product/_source/Ax7C123456789abc?_source_includes=price,stock,product_name
4. 更新库存(部分更新)
POST /product/_update/Ax7C123456789abc
{
"doc": {
"stock": 95
}
}
5. 减少库存(脚本更新)
POST /product/_update/Ax7C123456789abc
{
"script": {
"source": "ctx._source.stock -= params.quantity",
"lang": "painless",
"params": {
"quantity": 5
}
}
}
6. 检查商品是否售罄(查询库存)
GET /product/_source/Ax7C123456789abc?_source_includes=stock
7. 商品下架(删除)
DELETE /product/_doc/Ax7C123456789abc
5.3 日志记录生命周期管理
场景说明
系统日志的收集、查询、更新和定期清理。
操作示例
1. 存储错误日志
POST /logs/_doc
{
"timestamp": "2024-08-20T10:30:00Z",
"level": "ERROR",
"service": "payment-service",
"message": "Payment gateway timeout",
"details": {
"gateway": "alipay",
"retry_count": 3,
"transaction_id": "TXN123456"
},
"hostname": "server-01",
"ip_address": "192.168.1.100"
}
2. 查询特定日志
GET /logs/_doc/log_id_12345
3. 仅获取日志级别和消息(用于日志列表展示)
GET /logs/_source/log_id_12345?_source_includes=level,message,timestamp
4. 排除details字段(减少传输量)
GET /logs/_source/log_id_12345?_source_excludes=details
5. 更新日志处理状态
POST /logs/_update/log_id_12345
{
"doc": {
"status": "resolved",
"resolved_at": "2024-08-20T11:00:00Z"
}
}
6. 定期清理旧日志(按时间删除)
POST /logs/_delete_by_query
{
"query": {
"range": {
"timestamp": {
"lt": "2024-01-01T00:00:00Z"
}
}
}
}
7. 删除特定日志
DELETE /logs/_doc/log_id_old
5.4 订单记录管理
场景说明
订单系统的创建、查询、更新和归档。
操作示例
1. 创建订单(指定订单号作为ID)
PUT /orders/_doc/ORDER20240820001
{
"order_id": "ORDER20240820001",
"customer_id": 1001,
"items": [
{"product_id": "P001", "quantity": 2, "price": 299.00},
{"product_id": "P002", "quantity": 1, "price": 99.00}
],
"total_amount": 697.00,
"status": "pending",
"created_at": "2024-08-20T10:30:00Z",
"updated_at": "2024-08-20T10:30:00Z"
}
2. 查询订单详情
GET /orders/_doc/ORDER20240820001
3. 仅查询订单状态
GET /orders/_source/ORDER20240820001?_source_includes=status,total_amount
4. 更新订单状态(部分更新)
POST /orders/_update/ORDER20240820001
{
"doc": {
"status": "processing",
"updated_at": "2024-08-20T10:35:00Z"
}
}
5. 检查订单是否存在
HEAD /orders/_doc/ORDER20240820001
6. 订单完成后归档(删除)
DELETE /orders/_doc/ORDER20240820001
7. 批量删除已完成的订单
POST /orders/_delete_by_query
{
"query": {
"term": {
"status": "completed"
}
}
}
5.5 计数器/统计应用
场景说明
使用脚本的upsert功能实现计数器。
操作示例
1. 初始化或增加访问计数
POST /counter/_update/page_views_20240820
{
"script": {
"source": "ctx._source.count++",
"lang": "painless"
},
"upsert": {
"page": "/home",
"count": 1,
"date": "2024-08-20"
}
}
2. 查询计数
GET /counter/_doc/page_views_20240820
5.6 文档存在性检查应用
场景1:用户注册时检查用户名是否已存在
# 检查用户名对应的文档是否存在
HEAD /user/_doc/1001
# 根据返回状态码判断
# 200 OK - 用户已存在
# 404 Not Found - 用户不存在,可以注册
场景2:缓存预热前的数据检查
# 检查缓存数据是否存在于ES
HEAD /cache/_doc/cache_key_123
# 存在则从ES加载,不存在则从数据库加载
场景3:分布式锁的实现
# 1. 尝试创建锁文档(使用_create)
PUT /locks/_create/resource_123
{
"locked_by": "server-01",
"locked_at": "2024-08-20T10:30:00Z",
"ttl": 300
}
# 2. 检查锁是否存在
HEAD /locks/_doc/resource_123
# 3. 释放锁(删除)
DELETE /locks/_doc/resource_123
六、最佳实践建议
6.1 新增文档 - ID选择策略
| 场景 |
推荐方式 |
原因 |
示例 |
| 已有主键数据 |
指定ID |
保持业务逻辑一致性 |
用户ID、订单号 |
| 日志、事件数据 |
自动生成ID |
避免冲突,写入性能更好 |
系统日志、埋点数据 |
| 批量导入 |
根据数据源决定 |
有主键用主键,无主键自动生成 |
数据库迁移 |
| 分布式系统 |
自动生成ID |
避免ID冲突,提高并发性 |
微服务架构 |
| 需要防止覆盖 |
使用_create |
ID冲突时报错,确保数据安全 |
重要业务数据 |
6.2 更新文档 - 选择策略
| 场景 |
推荐方式 |
原因 |
示例 |
| 替换整个文档 |
全量更新(_doc) |
简单直接 |
用户资料完整更新 |
| 只更新部分字段 |
部分更新(_update + doc) |
保留其他字段 |
更新邮箱、状态等 |
| 计数器、累加器 |
脚本更新 |
支持数学运算 |
访问计数、库存扣减 |
| 存在则更新,不存在则创建 |
upsert |
一条命令搞定 |
用户首次登录统计 |
| 更新和创建字段不同 |
upsert(分离doc和upsert) |
灵活性高 |
登录记录初始化 |
| 批量更新 |
_bulk |
高效 |
批量更新状态 |
6.3 查询文档 - 性能优化
6.3.1 合理选择查询端点
| 场景 |
推荐端点 |
原因 |
| 需要版本信息 |
GET /_doc/id |
返回完整元数据 |
| 仅需要数据 |
GET /_source/id |
减少数据传输 |
| 检查存在性 |
HEAD /_doc/id |
最小网络开销 |
| 批量查询 |
GET /_msearch |
减少请求次数 |
6.3.2 字段过滤优化
// 仅返回需要的字段
GET /user/_source/1?_source_includes=name,email,phone
// 排除大字段(如文章内容、附件)
GET /article/_source/1?_source_excludes=content,attachments,comments
// 组合使用(实际应用较少)
GET /user/_source/1?_source_includes=name,email,phone&_source_excludes=password
6.3.3 批量查询优化
// 不推荐:多次单独查询
GET /user/_doc/1
GET /user/_doc/2
GET /user/_doc/3
// 推荐:使用msearch批量查询
GET /user/_msearch
{ "query": { "ids": { "values": ["1", "2", "3"] } } }
6.4 删除文档 - 最佳实践
6.4.1 批量删除优化
// 不推荐:多次单独删除
DELETE /user/_doc/1
DELETE /user/_doc/2
DELETE /user/_doc/3
// 推荐:使用bulk批量删除
POST /_bulk
{ "delete" : { "_index" : "user", "_id" : "1" } }
{ "delete" : { "_index" : "user", "_id" : "2" } }
{ "delete" : { "_index" : "user", "_id" : "3" } }
6.4.2 删除前检查
# 先检查文档是否存在
HEAD /user/_doc/1
# 根据检查结果决定是否删除
# 200 OK → 执行删除
# 404 Not Found → 跳过或记录日志
6.4.3 软删除策略
// 1. 标记为删除(软删除)
POST /user/_update/1
{
"doc": {
"is_deleted": true,
"deleted_at": "2024-08-20T10:30:00Z"
}
}
// 2. 查询时过滤已删除文档
GET /user/_search
{
"query": {
"bool": {
"must_not": {
"term": {
"is_deleted": true
}
}
}
}
}
// 3. 定期物理删除标记为删除的文档
POST /user/_delete_by_query
{
"query": {
"bool": {
"must": [
{ "term": { "is_deleted": true } },
{ "range": { "deleted_at": { "lt": "2024-07-01" } } }
]
}
}
}
6.4.4 大量删除策略
| 文档数量 |
推荐方式 |
原因 |
| 少量(<100) |
DELETE或bulk |
简单直接 |
| 中量(100-10000) |
_delete_by_query |
按条件批量删除 |
| 大量(>10000) |
重建索引 |
删除成本高,重建更快 |
6.5 通用性能优化建议
6.5.1 批量操作
// 批量导入
POST /_bulk
{ "index" : { "_index" : "user", "_id" : "1" } }
{ "name" : "张三", "age" : 20 }
{ "index" : { "_index" : "user", "_id" : "2" } }
{ "name" : "李四", "age" : 25 }
{ "index" : { "_index" : "user", "_id" : "3" } }
{ "name" : "王五", "age" : 30 }
6.5.2 刷新间隔设置
// 大批量导入前设置
PUT /user/_settings
{
"index": {
"refresh_interval": "-1" // 禁用自动刷新
}
}
// 导入完成后恢复
PUT /user/_settings
{
"index": {
"refresh_interval": "1s" // 恢复默认值
}
}
6.5.3 副本数优化
// 大批量导入前设置副本为0
PUT /user/_settings
{
"index": {
"number_of_replicas": 0
}
}
// 导入完成后恢复副本
PUT /user/_settings
{
"index": {
"number_of_replicas": 1
}
}
6.6 错误处理指南
| 错误类型 |
HTTP状态码 |
原因 |
解决方案 |
| 版本冲突 |
409 |
并发更新同一文档 |
使用_version或if_seq_no做乐观锁 |
| ID冲突(_create) |
409 |
使用_create时ID已存在 |
检查ID是否存在,或改用_doc端点 |
| 字段映射错误 |
400 |
数据类型与mapping不匹配 |
提前定义好索引mapping,检查数据格式 |
| 索引不存在 |
404 |
操作的索引不存在 |
先创建索引,使用PUT /index |
| 分片失败 |
503/500 |
节点故障或网络问题 |
检查集群健康状态,等待恢复 |
| 文档不存在(查询) |
404 |
查询的文档不存在 |
使用HEAD预检查,或处理not_found |
| 文档不存在(_update) |
404 |
更新的文档不存在 |
使用doc_as_upsert或upsert |
| 解析错误 |
400 |
JSON格式错误 |
检查JSON语法 |
6.7 并发控制策略
6.7.1 使用_version进行乐观锁
// 1. 先查询获取版本号
GET /user/_doc/1
// 返回 _version: 1
// 2. 更新时指定版本号
PUT /user/_doc/1?version=1
{
"name": "张三更新",
"age": 21
}
// 如果版本号不匹配,返回409错误
6.7.2 使用if_seq_no和if_primary_term
// 1. 先查询获取序列号
GET /user/_doc/1
// 返回 _seq_no: 0, _primary_term: 1
// 2. 更新时指定序列号
PUT /user/_doc/1?if_seq_no=0&if_primary_term=1
{
"name": "张三更新",
"age": 21
}
// 如果序列号不匹配,返回409错误
七、常用命令速查
7.1 文档新增操作
# 指定ID新增文档
PUT /index/_doc/id
POST /index/_doc/id
# 自动生成ID新增
POST /index/_doc
# 创建式新增(ID冲突时报错)
PUT /index/_create/id
# 示例
PUT /user/_doc/1001
{
"name": "张三",
"age": 20
}
7.2 文档更新操作
# 全量更新文档
PUT /index/_doc/id
POST /index/_doc/id
# 部分更新文档
POST /index/_update/id
{
"doc": { "field": "new_value" }
}
# 脚本更新
POST /index/_update/id
{
"script": {
"source": "ctx._source.field += params.amount",
"lang": "painless",
"params": { "amount": 1 }
}
}
# upsert(存在则更新,不存在则创建)
POST /index/_update/id
{
"doc": { "last_login": "2024-08-20" },
"upsert": { "username": "new_user", "created_at": "2024-08-20" }
}
# 按查询条件批量更新
POST /index/_update_by_query
{
"query": { "match": { "status": "pending" } },
"script": {
"source": "ctx._source.status = 'processed'",
"lang": "painless"
}
}
7.3 文档查询操作
# 查询完整文档(含元数据)
GET /index/_doc/id
# 检查文档是否存在
HEAD /index/_doc/id
# 仅查询文档数据
GET /index/_source/id
# 检查文档数据是否存在
HEAD /index/_source/id
# 查询指定字段(包含)
GET /index/_source/id?_source_includes=field1,field2
# 查询指定字段(排除)
GET /index/_source/id?_source_excludes=field1,field2
# 批量查询
GET /index/_msearch
# 示例
GET /user/_doc/1001
GET /user/_source/1001?_source_includes=name,email
HEAD /user/_doc/1001
7.4 文档删除操作
# 删除指定ID文档
DELETE /index/_doc/id
# 批量删除
POST /_bulk
{ "delete" : { "_index" : "index", "_id" : "id" } }
# 按查询条件删除
POST /index/_delete_by_query
{
"query": {
"match": {
"field": "value"
}
}
}
# 示例
DELETE /user/_doc/1001
POST /user/_delete_by_query
{
"query": {
"range": {
"created_at": {
"lt": "2024-01-01"
}
}
}
}
7.5 索引操作
# 创建索引
PUT /index
# 查看索引信息
GET /index
# 删除索引
DELETE /index
# 查看索引mapping
GET /index/_mapping
# 查看索引setting
GET /index/_settings
# 示例
PUT /user
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
7.6 批量操作
# 批量导入/更新/删除
POST /_bulk
# 示例:批量操作
POST /_bulk
{ "index" : { "_index" : "user", "_id" : "1" } }
{ "name" : "张三", "age" : 20 }
{ "create" : { "_index" : "user", "_id" : "2" } }
{ "name" : "李四", "age" : 25 }
{ "update" : { "_index" : "user", "_id" : "3" } }
{ "doc" : { "age" : 30 } }
{ "delete" : { "_index" : "user", "_id" : "4" } }
八、返回结果字段详解
8.1 新增/更新操作返回字段
| 字段 |
类型 |
说明 |
示例值 |
_index |
String |
文档所属的索引名称 |
"user" |
_type |
String |
文档类型,ES 7.x+统一为_doc |
"_doc" |
_id |
String |
文档唯一标识符 |
"1001" |
_version |
Number |
文档版本号,每次更新自动递增 |
1 |
result |
String |
操作结果 |
"created" / "updated" / "noop" |
_shards |
Object |
分片复制相关信息 |
见下表 |
_seq_no |
Number |
用于并发控制的序列号 |
0 |
_primary_term |
Number |
主分片的选举任期 |
1 |
get |
Object |
更新后的文档内容(仅_update操作) |
见下表 |
_shards字段说明
| 字段 |
说明 |
total |
总分片数(主分片+副本分片) |
successful |
成功执行的分片数 |
failed |
失败的分片数 |
get字段示例(_update操作)
"get": {
"found": true,
"_source": {
"name": "张三",
"age": 22
}
}
8.2 查询操作返回字段
| 字段 |
类型 |
说明 |
示例值 |
_index |
String |
文档所属的索引名称 |
"user" |
_type |
String |
文档类型 |
"_doc" |
_id |
String |
文档唯一标识符 |
"1001" |
_version |
Number |
文档版本号 |
1 |
_seq_no |
Number |
序列号 |
0 |
_primary_term |
Number |
主任期 |
1 |
found |
Boolean |
文档是否找到 |
true / false |
_source |
Object |
文档的实际数据 |
见下表 |
_source字段示例
{
"name": "张三",
"age": 20,
"gender": "男"
}
8.3 删除操作返回字段
| 字段 |
类型 |
说明 |
示例值 |
_index |
String |
索引名称 |
"user" |
_type |
String |
文档类型 |
"_doc" |
_id |
String |
文档ID |
"1001" |
_version |
Number |
版本号(删除后仍递增) |
2 |
result |
String |
操作结果 |
"deleted" / "not_found" |
_shards |
Object |
分片信息 |
见8.1节 |
_seq_no |
Number |
序列号 |
1 |
_primary_term |
Number |
主任期 |
1 |
8.4 result字段所有可能值
| 操作 |
result值 |
含义 |
| 新增 |
created |
文档创建成功 |
| 全量更新(ID存在) |
updated |
文档更新成功 |
| 全量更新(ID不存在) |
created |
文档创建成功 |
| 部分更新(有变更) |
updated |
文档部分更新成功 |
| 部分更新(无变更) |
noop |
无操作(值未变化) |
| 删除(文档存在) |
deleted |
文档删除成功 |
| 删除(文档不存在) |
not_found |
文档不存在 |
| 新增(_create,ID已存在) |
- |
返回409错误 |
| 部分更新(文档不存在) |
- |
返回404错误 |
九、注意事项
9.1 ES版本差异
| 版本 |
重要变化 |
影响 |
| ES 7.x |
_type字段固定为_doc |
不再支持自定义type |
| ES 8.x |
默认关闭索引类型 |
只能使用_doc |
| ES 7.x+ |
_create端点仍然支持 |
可以继续使用 |
| ES 7.x+ |
_update端点仍然支持 |
可以继续使用 |
9.2 ID设计原则
| 原则 |
说明 |
示例 |
| 长度控制 |
ID长度影响性能和存储 |
推荐不超过64字符 |
| 避免过长 |
过长ID降低查询性能 |
避免使用UUID作为ID |
| 推荐类型 |
数值型或短字符串 |
1001 / ORDER20240820 |
| 避免特殊字符 |
可能导致URL问题 |
避免使用/、?、#等 |
| 业务含义 |
有业务含义的ID便于调试 |
USER_1001 / ORDER_20240820_001 |
9.3 并发控制
并发控制机制对比
| 机制 |
字段 |
精确度 |
适用场景 |
| 版本号 |
_version |
中等 |
一般并发控制 |
| 序列号 |
_seq_no + _primary_term |
高 |
高并发场景 |
删除/更新操作的并发控制
- 删除/更新操作都会增加版本号
- 可以使用乐观锁防止误操作
- 操作前检查版本号或序列号
9.4 数据一致性
| 方面 |
说明 |
调整方式 |
| 近实时搜索 |
默认约1秒延迟 |
refresh_interval参数 |
| 刷新间隔 |
默认1秒 |
可调整为-1(禁用)或30s |
| 延迟删除 |
删除后稍后物理清理 |
等待段合并 |
| 副本同步 |
默认异步 |
可调整为wait_for_active_shards |
9.5 性能考虑
| 操作 |
性能建议 |
| 大量写入 |
禁用refresh、设置副本为0 |
| 批量操作 |
使用bulk API,每批1000-5000个文档 |
| 查询优化 |
使用_source过滤字段,减少传输 |
| 大量删除 |
考虑_delete_by_query或重建索引 |
| 大量更新 |
考虑_update_by_query或重建索引 |
| 并发控制 |
使用if_seq_no而非_version |
9.6 更新操作特别注意事项
| 注意事项 |
说明 |
| 全量更新覆盖 |
使用_doc更新会替换整个文档,未提供的字段会丢失 |
| 部分更新不存在 |
使用_update时文档不存在会返回404 |
| noop检测 |
默认开启,相同值更新会返回noop |
| 脚本性能 |
脚本更新比doc更新慢,复杂逻辑优先使用脚本 |
| 版本号递增 |
noop操作不会递增版本号 |
9.7 安全注意事项
| 方面 |
建议 |
| 权限控制 |
设置合适的索引级别权限 |
| 数据验证 |
写入前验证数据格式 |
| 敏感数据 |
敏感字段加密存储 |
| 审计日志 |
记录重要操作的审计日志 |
| 备份策略 |
定期备份重要索引数据 |
十、学习资源
10.1 官方文档
10.2 推荐阅读
| 主题 |
相关章节 |
| 基础概念 |
CRUD操作、索引管理 |
| 性能优化 |
批量操作、刷新策略、分片管理 |
| 并发控制 |
版本控制、序列号机制 |
| 数据建模 |
Mapping设计、字段类型选择 |
| 集群管理 |
节点配置、健康检查 |
| 脚本编程 |
Painless脚本语法、最佳实践 |
10.3 实践建议
-
学习路径
- 先掌握基础CRUD操作
- 学习查询和聚合
- 理解索引设计和性能优化
- 掌握集群运维
-
实践环境
- 使用Docker快速搭建本地环境
- 在测试环境充分验证后再上生产
- 监控集群健康状态
-
问题排查
- 查看ES日志
- 使用_cat API查看状态
- 分析慢查询日志
文档整理时间:2024年8月
基于《3.4 新增文档》、《3.5 查询文档和删除文档》、《3.6 更新文档》笔记整理
Elasticsearch 7.x/8.x 版本适用