Elasticsearch 使用手册

Elasticsearch / REST API / Query DSL / 搜索2026.10.2652 分钟阅读

索引创建、文档读写、全文检索、过滤、分页与聚合的常用写法。

REST API 与 DSL 的关系

Elasticsearch 通过 REST API 操作索引和文档,Query DSL 用 JSON 编写查询条件。下面的请求搜索名称包含 wireless 或 mouse 的商品,最多返回 10 条。在 Kibana 的 Dev Tools 中可以直接执行。

POST /es_manual_products/_search
{
  "query": { "match": { "name": "wireless mouse" } },
  "size": 10
}
概念含义本手册示例
index存放文档并定义映射和设置的逻辑集合es_manual_products
document / _id一条 JSON 文档及其索引内标识商品文档 / p001
field / mapping文档字段及其类型、索引方式price_cents 为 long
shard / replica索引的分片及其副本由集群设置和索引设置管理
_source返回文档内容的来源字段商品名、价格等 JSON 内容
目的方法与路径说明
查看版本GET /读取 version.number
创建索引PUT /es_manual_products显式提供 mapping
写入指定 IDPUT /es_manual_products/_doc/p001存在时替换文档内容
仅创建PUT /es_manual_products/_create/p001已存在则返回 409
读取文档GET /es_manual_products/_doc/p001按 ID 获取
局部更新POST /es_manual_products/_update/p001通过 doc 或 script 更新
删除文档DELETE /es_manual_products/_doc/p001只删除该 ID
搜索 / 批量POST /es_manual_products/_search 或 /_bulkJSON 查询 / NDJSON 批量操作

搜索请求使用 GET 或 POST,query 指定查询条件,sort 指定排序,aggs 定义聚合,_source 选择返回字段。

在终端使用 curl

export ES_URL='https://localhost:9200'
export ES_CA='/path/to/http_ca.crt'
# ES_API_KEY 由运行环境注入,值为编码后的 API Key。
curl --cacert "$ES_CA" -H "Authorization: ApiKey $ES_API_KEY" "$ES_URL/"

curl --cacert "$ES_CA" \
  -H "Authorization: ApiKey $ES_API_KEY" \
  -H 'Content-Type: application/json' \
  -X POST "$ES_URL/es_manual_products/_search" \
  --data-binary '{"query":{"match_all":{}},"size":10}'

将 ES_URL 和 ES_CA 换成集群地址与证书路径。使用用户名密码认证时,把认证头换成 -u 用户名,curl 会提示输入密码。

创建索引与字段映射

PUT /es_manual_products
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "product_id": { "type": "keyword" },
      "name": {
        "type": "text",
        "analyzer": "standard",
        "fields": { "keyword": { "type": "keyword" } }
      },
      "description": { "type": "text", "analyzer": "standard" },
      "category": { "type": "keyword" },
      "brand": { "type": "keyword" },
      "price_cents": { "type": "long" },
      "stock": { "type": "integer" },
      "status": { "type": "keyword" },
      "tags": { "type": "keyword" },
      "created_at": { "type": "date" }
    }
  }
}

GET /es_manual_products/_mapping
类型用途例子
text分词后的全文检索商品名、描述
keyword精确过滤、排序和分组品牌、状态、业务 ID
integer / long整数及范围比较库存、以分为单位的价格
date时间范围、时间排序和时间分桶带时区的 ISO 8601 时间
boolean真假状态是否启用等二值字段

name 用于全文检索,name.keyword 用于完整名称匹配和排序,两者取自同一个 name 值。price_cents 以分为单位,19900 表示 199.00 元。tags 可以直接写入字符串数组。

dynamic: strict 拒绝未声明的字段。已有字段要改类型,需要新建索引并迁移数据。

Bulk 批量导入

POST /es_manual_products/_bulk?refresh=wait_for
{"index":{"_id":"p001"}}
{"product_id":"p001","name":"Wireless Mouse","description":"Quiet wireless mouse for office","category":"accessories","brand":"Acme","price_cents":9900,"stock":30,"status":"active","tags":["wireless","office"],"created_at":"2026-10-01T09:00:00+08:00"}
{"index":{"_id":"p002"}}
{"product_id":"p002","name":"Wireless Keyboard","description":"Compact keyboard for office","category":"accessories","brand":"Acme","price_cents":19900,"stock":12,"status":"active","tags":["wireless","office"],"created_at":"2026-10-02T09:00:00+08:00"}
{"index":{"_id":"p003"}}
{"product_id":"p003","name":"Gaming Mouse","description":"Wired mouse for gaming","category":"accessories","brand":"Nova","price_cents":29900,"stock":0,"status":"inactive","tags":["gaming"],"created_at":"2026-10-03T09:00:00+08:00"}
{"index":{"_id":"p004"}}
{"product_id":"p004","name":"USB Hub","description":"Multiport adapter for laptop","category":"adapters","brand":"Nova","price_cents":15900,"stock":8,"status":"active","tags":["office"],"created_at":"2026-10-04T09:00:00+08:00"}

Bulk 使用 NDJSON,每行一条 JSON:第一行写动作,第二行写文档,最后保留一个换行符。delete 只有动作行;update 的第二行写 doc 或 script。

Bulk 会分别处理每个动作。收到 HTTP 200 后,还要检查 errors 和 items 中的 status/error,重试失败项。脚本累加等操作重复执行会再次修改数据。

用 curl 导入时,将数据行保存为 products.ndjson,去掉 POST 首行。

curl --cacert "$ES_CA" \
  -H "Authorization: ApiKey $ES_API_KEY" \
  -H 'Content-Type: application/x-ndjson' \
  -X POST "$ES_URL/es_manual_products/_bulk?refresh=wait_for" \
  --data-binary @products.ndjson
GET /es_manual_products/_count

GET /es_manual_products/_doc/p001

导入后,_count 为 4,读取 p001 返回 found: true。重新导入相同 ID 会替换对应文档。

写入成功与搜索可见

按 ID 执行 GET 可以立即读到新写入的文档,搜索则要等 refresh。写入时加上 refresh=wait_for,会等到文档可被搜索后再返回。

批量写入通常省略 refresh 参数。refresh=true 会主动刷新相关分片,频繁使用会增加写入开销。

搜索请求与响应结构

POST /es_manual_products/_search
{
  "query": { "match_all": {} },
  "_source": ["product_id", "name", "price_cents"],
  "from": 0,
  "size": 2,
  "sort": [{ "product_id": "asc" }],
  "track_total_hits": true
}

请求返回 p001、p002,总数为 4。下面截取响应中的一条文档:

{
  "took": 2,
  "timed_out": false,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 },
  "hits": {
    "total": { "value": 4, "relation": "eq" },
    "max_score": null,
    "hits": [
      {
        "_index": "es_manual_products",
        "_id": "p001",
        "_score": null,
        "_source": { "product_id": "p001", "name": "Wireless Mouse", "price_cents": 9900 },
        "sort": ["p001"]
      }
    ]
  }
}

hits.hits 保存当前页文档,hits.total 表示匹配总量。relation 为 eq 表示精确值,gte 表示至少有这么多条;需要精确计数时设置 track_total_hits: true。_score 是相关性得分,按字段排序时通常为 null。

took 是服务端处理耗时。timed_out 表示是否超时,_shards.failed 是失败的分片数。_source 只控制返回哪些字段。

全文检索:match、match_phrase 与 multi_match

match:按词项匹配

POST /es_manual_products/_search
{
  "query": {
    "match": {
      "name": { "query": "wireless mouse", "operator": "and" }
    }
  }
}

match 先对查询文本分词。operator 默认是 or,wireless mouse 会匹配 p001、p002、p003;改成 and 后,两个词都要出现,只匹配 p001。

match_phrase:限制词项顺序与位置

POST /es_manual_products/_search
{
  "query": { "match_phrase": { "name": "wireless mouse" } }
}

match_phrase 按词项的顺序和位置匹配短语,slop 默认为 0。整个名称精确匹配使用 name.keyword。

multi_match:同时搜索多个字段

POST /es_manual_products/_search
{
  "query": {
    "multi_match": {
      "query": "wireless",
      "fields": ["name^3", "description"],
      "type": "best_fields"
    }
  }
}

name^3 将名称字段的权重设为 3。best_fields 取匹配最好的字段计算得分,名称和描述都可以命中。

通过 _analyze 检查分词

POST /es_manual_products/_analyze
{
  "field": "name",
  "text": "Wireless Mouse"
}

返回的词项是 wireless 和 mouse。中文分词也可以用 _analyze 查看,选分析器时用实际搜索词试一下结果。

精确条件:term、terms、range 与 exists

查询示例片段语义
term{"term":{"brand":"Acme"}}匹配一个精确值
terms{"terms":{"brand":["Acme","Nova"]}}匹配集合中的任意值
range{"range":{"price_cents":{"gte":10000,"lt":20000}}}大于等于 100 元且小于 200 元
exists{"exists":{"field":"tags"}}字段存在可检索的值
POST /es_manual_products/_search
{
  "query": { "term": { "name.keyword": "Wireless Mouse" } }
}

term 直接匹配词项。这里 brand 的 Acme 与 acme 是两个值;name 经过分词,完整名称要查 name.keyword。

terms 匹配列表中的任意值。exists 检查字段是否有索引值:null 和空数组通常算缺失,空字符串算存在。

POST /es_manual_products/_search
{
  "query": {
    "range": {
      "created_at": {
        "gte": "2026-10-01T00:00:00+08:00",
        "lt": "2026-11-01T00:00:00+08:00"
      }
    }
  }
}

gte 包含起点,lt 排除终点,这样可以查完整个十月。最近七天可以写 now-7d,今天零点可以写 now/d;按本地日期查询时指定时区。

bool:组合检索与业务过滤

子句是否要求匹配评分行为
must每一项都必须匹配在查询上下文中参与评分
filter每一项都必须匹配不参与评分
must_not每一项都不得匹配不参与评分
should由 minimum_should_match 决定至少匹配几项在查询上下文中可增加评分
POST /es_manual_products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "wireless" } }
      ],
      "filter": [
        { "term": { "status": "active" } },
        { "range": { "price_cents": { "lte": 20000 } } },
        { "range": { "stock": { "gt": 0 } } }
      ],
      "must_not": [
        { "term": { "tags": "refurbished" } }
      ],
      "should": [
        { "term": { "brand": "Acme" } }
      ],
      "minimum_should_match": 0
    }
  }
}

结果为 p001、p002:名称包含 wireless,已上架、有库存,价格不超过 200 元。Acme 品牌加分,refurbished 标签被排除。状态、分类和价格范围等条件放在 filter 中,不计算相关性得分。

有 must 或 filter 时,minimum_should_match 默认是 0,should 只用于加分。只有 should 时默认是 1。下面要求品牌和标签至少匹配一项,所以显式设为 1。

POST /es_manual_products/_search
{
  "query": {
    "bool": {
      "filter": [{ "term": { "status": "active" } }],
      "should": [
        { "term": { "brand": "Acme" } },
        { "term": { "tags": "gaming" } }
      ],
      "minimum_should_match": 1
    }
  }
}

排序、分页与高亮

浅分页:from 与 size

POST /es_manual_products/_search
{
  "query": { "term": { "status": "active" } },
  "sort": [{ "price_cents": "asc" }, { "product_id": "asc" }],
  "from": 0,
  "size": 2,
  "_source": ["product_id", "name", "price_cents"]
}

结果按价格升序返回 p001、p004,同价时再按 product_id 排序。第 n 页的 from 为 (n - 1) × size。文本排序使用 keyword 字段。

from + size 默认最多为 10000。连续翻页或导出使用 search_after,配合 PIT 固定查询期间的数据视图。

深分页:PIT 与 search_after

POST /es_manual_products/_pit?keep_alive=1m

POST /_search
{
  "pit": { "id": "PIT_ID", "keep_alive": "1m" },
  "query": { "match_all": {} },
  "sort": [{ "product_id": "asc" }, { "_shard_doc": "asc" }],
  "size": 2
}

PIT_ID 填创建请求返回的 id。下一页保持 query、sort 不变,把上一页最后一条的完整 sort 数组填入 search_after。响应有新的 pit_id 时使用新值。下面假设 sort 为 ["p002", 1],执行时换成实际返回值。

POST /_search
{
  "pit": { "id": "LATEST_PIT_ID", "keep_alive": "1m" },
  "query": { "match_all": {} },
  "sort": [{ "product_id": "asc" }, { "_shard_doc": "asc" }],
  "search_after": ["p002", 1],
  "size": 2
}

DELETE /_pit
{
  "id": "LATEST_PIT_ID"
}

PIT 搜索使用 /_search。_shard_doc 用于区分排序值相同的文档,keep_alive 设置 PIT 的保留时间。逐页传递游标,结束后用 DELETE /_pit 关闭;过期后重新创建并开始查询。

高亮:返回命中文本片段

POST /es_manual_products/_search
{
  "query": { "match": { "name": "wireless" } },
  "highlight": {
    "pre_tags": ["[hit]"],
    "post_tags": ["[/hit]"],
    "fields": { "name": {} }
  }
}

高亮片段放在每条结果的 highlight.name 数组中。前端先转义文本,再把 [hit] 标记转换为高亮样式。

聚合:分组、指标与时间统计

按分类统计上架商品

POST /es_manual_products/_search
{
  "size": 0,
  "query": { "term": { "status": "active" } },
  "aggs": {
    "by_category": {
      "terms": { "field": "category", "size": 10 },
      "aggs": {
        "avg_price_cents": { "avg": { "field": "price_cents" } },
        "stock_total": { "sum": { "field": "stock" } }
      }
    }
  }
}

size: 0 只返回统计结果。query 筛出 active 商品,aggs 对这些商品分组,不受分页影响。每个桶的 key 是分类名,doc_count 是商品数,子聚合计算均价和库存。

accessories 有 2 条,均价 14900 分、库存 42;adapters 有 1 条,均价 15900 分、库存 8。

terms 返回数量最多的前 N 个桶,多分片下计数可能有误差,可通过 shard_size 增加候选桶。遍历全部分组使用 composite 和 after_key。分组字段使用 keyword。

按业务时区统计每日新增量

POST /es_manual_products/_search
{
  "size": 0,
  "query": {
    "range": {
      "created_at": {
        "gte": "2026-10-01T00:00:00+08:00",
        "lt": "2026-10-08T00:00:00+08:00"
      }
    }
  },
  "aggs": {
    "by_day": {
      "date_histogram": {
        "field": "created_at",
        "calendar_interval": "day",
        "time_zone": "Asia/Shanghai",
        "min_doc_count": 0,
        "extended_bounds": { "min": "2026-10-01", "max": "2026-10-07" }
      }
    }
  }
}

calendar_interval 按日历分桶,time_zone 指定时区。min_doc_count: 0 配合 extended_bounds 补齐没有数据的日期,query 控制统计范围。筛选商品列表又想保留筛选前的分组统计时,可用 post_filter 过滤命中结果。

文档写入、局部更新与删除

创建 p-demo,将价格从 5000 改为 4500,再读取文档。

PUT /es_manual_products/_create/p-demo?refresh=wait_for
{
  "product_id": "p-demo",
  "name": "Demo Mouse",
  "price_cents": 5000,
  "stock": 10,
  "status": "active"
}

POST /es_manual_products/_update/p-demo?refresh=wait_for
{
  "doc": { "price_cents": 4500 }
}

GET /es_manual_products/_doc/p-demo

_create 遇到已有 ID 返回 409。PUT /_doc/ID 替换整个文档,_update 的 doc 只更新指定字段,所以改价后 stock 仍为 10。

脚本更新与 upsert

POST /es_manual_products/_update/p-demo?refresh=wait_for
{
  "script": {
    "lang": "painless",
    "source": "ctx._source.stock += params.delta",
    "params": { "delta": 2 }
  }
}

脚本每执行一次,库存增加 2。文档不存在时也要写入,可设置 doc_as_upsert: true,例如 {"doc":{"product_id":"p-demo","stock":10},"doc_as_upsert":true}。

并发修改:避免覆盖其他写入

GET 文档后,将 _seq_no 和 _primary_term 填入下面的 SEQ_NO、PRIMARY_TERM。期间若有其他写入,更新返回 409,需要重新读取后处理冲突。

POST /es_manual_products/_update/p-demo?if_seq_no=SEQ_NO&if_primary_term=PRIMARY_TERM&refresh=wait_for
{
  "doc": { "price_cents": 4300 }
}

DELETE /es_manual_products/_doc/p-demo?refresh=wait_for

DELETE 删除 p-demo。

对象数组:何时需要 nested

商品有两种规格:红色 S 码、蓝色 L 码。要让颜色和尺码匹配同一个规格,将 variants 定义为 nested。普通 object 数组会展开字段,可能把不同规格的值匹配到一起。

PUT /es_manual_variants
{
  "mappings": {
    "properties": {
      "variants": {
        "type": "nested",
        "properties": {
          "color": { "type": "keyword" },
          "size": { "type": "keyword" }
        }
      }
    }
  }
}

PUT /es_manual_variants/_doc/v001?refresh=wait_for
{
  "variants": [
    { "color": "red", "size": "S" },
    { "color": "blue", "size": "L" }
  ]
}

POST /es_manual_variants/_search
{
  "query": {
    "nested": {
      "path": "variants",
      "score_mode": "none",
      "query": {
        "bool": {
          "filter": [
            { "term": { "variants.color": "red" } },
            { "term": { "variants.size": "L" } }
          ]
        }
      }
    }
  }
}

red + L 没有结果,改成 red + S 会返回 v001。两个条件放在同一个 nested 查询里,匹配同一条规格。

索引维护:别名与映射迁移

给商品索引加上 es_manual_catalog 别名,应用通过别名读写,迁移时再切换它指向的索引。

POST /_aliases
{
  "actions": [
    { "add": { "index": "es_manual_products", "alias": "es_manual_catalog", "is_write_index": true } }
  ]
}

GET /_alias/es_manual_catalog

先用前面的 mapping 创建 es_manual_products_v2,改好字段类型,再执行 _reindex。目标索引的 mapping 和 settings 需要单独配置。

POST /_reindex
{
  "source": { "index": "es_manual_products" },
  "dest": { "index": "es_manual_products_v2" }
}

迁移时暂停写入,或同步这段时间的增量数据。完成后检查 failures、文档数和查询结果,再切换别名。

POST /_aliases
{
  "actions": [
    { "remove": { "index": "es_manual_products", "alias": "es_manual_catalog" } },
    { "add": { "index": "es_manual_products_v2", "alias": "es_manual_catalog", "is_write_index": true } }
  ]
}

将移除旧索引和添加新索引放在同一个请求中完成切换。保留旧索引便于回退。

常见问题与诊断顺序

现象优先检查处理方向
401 / 403认证信息 / 角色权限区分未认证与无权执行该操作
400 解析或映射错误error.type、reason、字段类型、JSON 格式修正请求或数据,不盲目重试
404索引名、文档 ID、别名指向确认资源是否存在
409_create 重复 ID、并发条件过期确认预期或重新读取并合并
429写入并发、拒绝数、节点资源退避重试并降低并发或批次大小
写入后搜不到Bulk 单项失败、refresh、查询字段类型先按 ID 读取,再检查映射和分词
排序或聚合报 text 错误是否误用了 text 字段改用 keyword 子字段
should 没有缩小结果集是否同时存在 must/filter显式设置 minimum_should_match
单节点 yellow副本无法分配到同一个节点测试环境可调整副本;生产按冗余需求扩容和排查
分页重复或遗漏排序键、索引刷新、PIT 生命周期稳定排序并正确传递 PIT 与完整 sort 数组
GET /_cluster/health

GET /_cat/indices/es_manual_*?v

GET /es_manual_products/_mapping

POST /es_manual_products/_validate/query?explain=true
{
  "query": { "match": { "name": "wireless" } }
}

POST /es_manual_products/_explain/p001
{
  "query": { "match": { "name": "wireless" } }
}

_validate/query 检查查询语法,_explain 查看文档的匹配和评分过程。分析查询耗时可加入 profile: true。

查询结果速查

验证动作初始数据上的预期
GET /es_manual_products/_count4 条文档
match name: wireless mouse,operator: and仅 p001
term name.keyword: Wireless Mouse仅 p001
active 且价格升序,size: 2p001、p004
active 按 category 聚合accessories: 2,adapters: 1
PIT 每页 2 条完整遍历四个不同 ID,无遗漏重复
nested 查询 red + L0 条;red + S 为 1 条
— 本页完 —
← 返回笔记列表

搜索内容

输入关键词搜索文章、笔记与随笔。