索引与文档操作
ES 中最基本的操作:索引管理、文档 CRUD、Bulk 批量写入、Reindex 数据迁移和 Alias 别名切换。
索引操作
创建索引
创建时可指定 Settings 和 Mappings:
PUT /user
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "1s"
},
"mappings": {
"properties": {
"info": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"email": {
"type": "keyword",
"index": false
},
"name": {
"type": "object",
"properties": {
"first_name": { "type": "keyword" },
"last_name": { "type": "keyword" }
}
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}| Settings 参数 | 说明 | 默认值 |
|---|---|---|
number_of_shards |
主分片数(创建后不可改) | 1(ES 7.x+) |
number_of_replicas |
每个主分片的副本数 | 1 |
refresh_interval |
刷新间隔(数据可搜索的延迟) | 1s |
max_result_window |
from + size 上限 |
10000 |
查看索引
# 查看索引 Mapping 和 Settings
GET /user
# 仅查看 Mapping
GET /user/_mapping
# 仅查看 Settings
GET /user/_settings
# 查看所有索引
GET /_cat/indices?v
# 查看索引健康状态
GET /_cluster/health/user删除索引
DELETE /user🚨 陷阱:删除索引是物理删除,不可恢复。生产环境建议通过 Alias 切换来"废弃"旧索引,确认没问题后再物理删除。
修改索引 Mapping
# ✅ 允许:添加新字段
PUT /user/_mapping
{
"properties": {
"age": { "type": "integer" }
}
}# ❌ 禁止:修改已有字段的类型或分词器
PUT /user/_mapping
{
"properties": {
"age": { "type": "keyword" } // 报错!age 已经是 integer
}
}🚨 陷阱:已有字段的 Mapping 不能修改。唯一的办法是 Reindex:
- 创建新索引(正确的 Mapping)
- Reindex 迁移数据
- Alias 切换到新索引
查看分词效果(Analyzer API)
# 测试指定分词器对文本的切分结果
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "中华人民共和国"
}# 测试索引中某字段实际使用的分词器
GET /user/_analyze
{
"field": "info",
"text": "测试文本"
}文档 CRUD
添加文档
# 指定文档 ID
POST /user/_doc/1
{
"age": 18,
"email": "123456@qq.com",
"info": "测试数据",
"name": {
"first_name": "John",
"last_name": "Hank"
}
}
# 自动生成文档 ID(POST 不带 ID)
POST /user/_doc
{
"age": 25,
"info": "自动生成ID"
}| 方法 | 行为 |
|---|---|
POST /{index}/_doc |
自动生成 ID(推荐) |
POST /{index}/_doc/{id} |
指定 ID,已存在则覆盖 |
PUT /{index}/_doc/{id} |
指定 ID,已存在则覆盖 |
💡 最佳实践:让 ES 自动生成文档 ID 能提升写入性能——ES 无需额外查找确认 ID 是否已存在。
查询文档
# 按 ID 查询
GET /user/_doc/1
# 只返回部分字段
GET /user/_doc/1?_source=name,email
# 不返回 _source,只检查是否存在
HEAD /user/_doc/1 # 200 = 存在,404 = 不存在删除文档
DELETE /user/_doc/1修改文档
全量替换(PUT)
PUT /user/_doc/1
{
"age": 18,
"email": "123456@qq.com",
"info": "全量替换的新内容",
"name": {
"first_name": "John updated",
"last_name": "Hank"
}
}🚨 陷阱:
PUT是全量替换——未在请求体中出现的字段会被删除。如果你只传{"age": 20},文档变成只有age字段。
增量修改(_update)
POST /user/_update/1
{
"doc": {
"age": 35
}
}Upsert — 存在则更新,不存在则插入
POST /user/_update/1
{
"doc": {
"age": 30,
"info": "updated"
},
"doc_as_upsert": true
}
# 或更灵活的 upsert
POST /user/_update/1
{
"script": {
"source": "ctx._source.views += params.count",
"params": { "count": 1 }
},
"upsert": {
"views": 1,
"name": "新文档"
}
}🔬 深入原理:ES 文档是不可变的(Immutable)。即便是
_update,底层也是(1)从_source读取旧文档 →(2)合并变更 →(3)标记旧文档删除 →(4)写入新文档。不可变性带来了无锁写入和高效的缓存利用。
使用 Script 更新
# 增加数值
POST /user/_update/1
{
"script": {
"source": "ctx._source.age += params.increment",
"params": { "increment": 1 }
}
}
# 条件更新(age < 50 时才更新)
POST /user/_update/1
{
"script": {
"source": "if (ctx._source.age < 50) { ctx._source.age += 1 }"
}
}
# 添加数组元素(去重)
POST /user/_update/1
{
"script": {
"source": "if (!ctx._source.tags.contains(params.tag)) { ctx._source.tags.add(params.tag) }",
"params": { "tag": "vip" }
}
}Bulk API — 批量操作
一次请求执行多个操作,大幅减少网络往返。
POST /_bulk
{ "index": { "_index": "user", "_id": "1" } }
{ "name": "张三", "age": 25 }
{ "create": { "_index": "user", "_id": "2" } }
{ "name": "李四", "age": 30 }
{ "update": { "_index": "user", "_id": "1" } }
{ "doc": { "age": 26 } }
{ "delete": { "_index": "user", "_id": "2" } }| 操作 | 说明 | 行为 |
|---|---|---|
index |
索引文档 | 存在则覆盖 |
create |
创建文档 | 存在则报错 |
update |
更新文档 | doc + script |
delete |
删除文档 | 不存在也不报错 |
💡 最佳实践:
- Bulk 体使用
application/x-ndjson,每行一个 JSON- 推荐单批次 5-15MB(物理大小),过大反而增加内存压力
- 大量写入时关闭
refresh_interval(设为-1),写完再恢复- 使用
create而非index可防止意外覆盖已有文档
Bulk 响应解读
{
"took": 30,
"errors": false,
"items": [
{ "index": { "_id": "1", "status": 201, "result": "created" } },
{ "update": { "_id": "1", "status": 200, "result": "updated" } }
]
}"errors": false 不意味着所有操作都成功——需检查每个 item 中的 status。
Reindex — 数据迁移
将一个索引的文档复制到另一个索引,常用于 Mapping 变更。
基础用法
POST /_reindex
{
"source": { "index": "user" },
"dest": { "index": "user_v2" }
}高级用法
POST /_reindex
{
"source": {
"index": "user",
"query": {
"range": {
"age": { "gte": 18 }
}
},
"_source": ["name", "email"] // 只复制指定字段
},
"dest": {
"index": "user_v2",
"version_type": "external" // 保留源版本号
},
"script": {
"source": """
ctx._source.full_name = ctx._source.name.first_name
+ ' ' + ctx._source.name.last_name;
ctx._source.remove('name');
""",
"lang": "painless"
}
}| 常用场景 | 说明 |
|---|---|
| 修改 Mapping | 创建正确 Mapping 的新索引 → Reindex → Alias 切换 |
| 调整分片数 | 新索引指定新分片数 → Reindex |
| 数据清洗 | Reindex 时用 script 清洗/转换字段 |
| 跨集群迁移 | 配合 Remote Cluster(CCR)使用 |
💡 最佳实践:Reindex 异步执行,大索引用时较长。可通过
GET /_tasks?detailed=true&actions=*reindex监控进度。
Alias — 索引别名
Alias 是索引的"软链接",可实现零停机切换。
创建别名
# 单索引别名
POST /_aliases
{
"actions": [
{ "add": { "index": "user_v1", "alias": "user" } }
]
}
# 多索引别名(查询时同时查多个索引)
POST /_aliases
{
"actions": [
{ "add": { "index": "logs-2025-01", "alias": "logs" } },
{ "add": { "index": "logs-2025-02", "alias": "logs" } }
]
}零停机切换
POST /_aliases
{
"actions": [
{ "remove": { "index": "user_v1", "alias": "user" } },
{ "add": { "index": "user_v2", "alias": "user" } }
]
}两个 action 是原子的 —— 应用始终通过 user 别名访问,不会有任何中断。
Filtered Alias — 带过滤条件的别名
POST /_aliases
{
"actions": [
{
"add": {
"index": "user",
"alias": "vip_users",
"filter": { "term": { "level": "vip" } }
}
}
]
}💡 最佳实践:通过 Filtered Alias 可以在不修改应用代码的情况下,实现不同用户看到不同数据范围。
Write Index — 指定写入目标
当别名指向多个索引时,需指定哪个索引接收写入:
POST /_aliases
{
"actions": [
{ "add": { "index": "logs-2025-01", "alias": "logs", "is_write_index": false } },
{ "add": { "index": "logs-2025-02", "alias": "logs", "is_write_index": true } }
]
}Index Template — 索引模板
自动为新创建的索引应用 Settings 和 Mappings(ES 7.8+ 推荐使用 Composable Index Template)。
新版 Composable Template(ES 7.8+)
PUT /_index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" },
"level": { "type": "keyword" }
}
}
},
"priority": 200,
"composed_of": ["common_settings"]
}| 参数 | 说明 |
|---|---|
index_patterns |
匹配的索引名模式 |
priority |
优先级(多个模板匹配时,取最高) |
composed_of |
引用 Component Template(模块化复用) |
Component Template — 可复用的模块
# 先创建可复用的组件
PUT /_component_template/common_settings
{
"template": {
"settings": {
"number_of_replicas": 1,
"refresh_interval": "30s"
}
}
}💡 最佳实践:将 Settings、Mappings 拆成 Component Template,再组合成 Index Template。如
common_settings+logs_mappings→logs_template。
常见陷阱
| 陷阱 | 说明 |
|---|---|
| 🚨 索引删除不可恢复 | 没有回收站,物理删除 |
| 🚨 Mapping 字段不可修改 | 类型/分词器锁定,只能 Reindex |
🚨 PUT 全量覆盖 |
未传的字段会被删除;建议用 POST /_update |
🚨 _update 文档必须存在 |
不存在报 404;用 upsert 处理 |
🚨 Bulk index vs create |
index 会覆盖,create 发现已存在会报错 |
| 🚨 分片数创建后不可改 | 必须 Reindex 到新索引 |
🚨 _source 关闭后无法 Reindex |
Reindex 依赖 _source |
| 🚨 Dynamic Mapping 猜错类型 | 生产环境建议显式定义 Mapping 或用 Dynamic Template |