Skip to content
ES
索引与文档操作

索引与文档操作

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

  1. 创建新索引(正确的 Mapping)
  2. Reindex 迁移数据
  3. 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_mappingslogs_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