Elasticsearch ES|QL:逻辑视图、子查询与 Schema-on-Read

1. 概述

  • 逻辑视图(Logical Views)------将常用查询定义为命名视图,一处定义,处处引用。
  • 子查询(Subqueries) ------在单个 FROM 中组合多个索引,各自拥有独立的处理管道,即使它们的 schema 互不兼容。
  • Schema-on-Read ------无需修改映射(mapping)或重建索引(reindex),即可查询那些从未被映射过的字段,直接从 _source 中按需读取。

这些特性共同构建了一种更灵活、更敏捷的数据建模与查询方式。


2. 三大核心能力概览:

在深入每个特性之前,先理解它们之间的组合关系 至关重要。这三者设计为分层叠加(layer together)的架构:
#mermaid-svg-SMU1YjcOlfQfRWmA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SMU1YjcOlfQfRWmA .error-icon{fill:#552222;}#mermaid-svg-SMU1YjcOlfQfRWmA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SMU1YjcOlfQfRWmA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SMU1YjcOlfQfRWmA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SMU1YjcOlfQfRWmA .marker.cross{stroke:#333333;}#mermaid-svg-SMU1YjcOlfQfRWmA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SMU1YjcOlfQfRWmA p{margin:0;}#mermaid-svg-SMU1YjcOlfQfRWmA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster-label text{fill:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster-label span{color:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster-label span p{background-color:transparent;}#mermaid-svg-SMU1YjcOlfQfRWmA .label text,#mermaid-svg-SMU1YjcOlfQfRWmA span{fill:#333;color:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA .node rect,#mermaid-svg-SMU1YjcOlfQfRWmA .node circle,#mermaid-svg-SMU1YjcOlfQfRWmA .node ellipse,#mermaid-svg-SMU1YjcOlfQfRWmA .node polygon,#mermaid-svg-SMU1YjcOlfQfRWmA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SMU1YjcOlfQfRWmA .rough-node .label text,#mermaid-svg-SMU1YjcOlfQfRWmA .node .label text,#mermaid-svg-SMU1YjcOlfQfRWmA .image-shape .label,#mermaid-svg-SMU1YjcOlfQfRWmA .icon-shape .label{text-anchor:middle;}#mermaid-svg-SMU1YjcOlfQfRWmA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SMU1YjcOlfQfRWmA .rough-node .label,#mermaid-svg-SMU1YjcOlfQfRWmA .node .label,#mermaid-svg-SMU1YjcOlfQfRWmA .image-shape .label,#mermaid-svg-SMU1YjcOlfQfRWmA .icon-shape .label{text-align:center;}#mermaid-svg-SMU1YjcOlfQfRWmA .node.clickable{cursor:pointer;}#mermaid-svg-SMU1YjcOlfQfRWmA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SMU1YjcOlfQfRWmA .arrowheadPath{fill:#333333;}#mermaid-svg-SMU1YjcOlfQfRWmA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SMU1YjcOlfQfRWmA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SMU1YjcOlfQfRWmA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SMU1YjcOlfQfRWmA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SMU1YjcOlfQfRWmA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SMU1YjcOlfQfRWmA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster text{fill:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA .cluster span{color:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SMU1YjcOlfQfRWmA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SMU1YjcOlfQfRWmA rect.text{fill:none;stroke-width:0;}#mermaid-svg-SMU1YjcOlfQfRWmA .icon-shape,#mermaid-svg-SMU1YjcOlfQfRWmA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SMU1YjcOlfQfRWmA .icon-shape p,#mermaid-svg-SMU1YjcOlfQfRWmA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SMU1YjcOlfQfRWmA .icon-shape .label rect,#mermaid-svg-SMU1YjcOlfQfRWmA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SMU1YjcOlfQfRWmA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SMU1YjcOlfQfRWmA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SMU1YjcOlfQfRWmA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 逻辑视图

(命名查询)
子查询管道

(每个索引独立处理)
Schema-on-Read

(读取未映射字段)
统一结果集

(合并输出)

  • 子查询 负责为每个索引定义独立的管道(WHEREEVALKEEP 等),处理各自特有的字段和逻辑。
  • 视图将子查询打包,对外暴露一个单一名称,消费者(仪表板、告警、临时查询)只需引用视图名,无需关心内部复杂性。
  • Schema-on-Read 赋予这些管道访问从未在映射中声明 的字段的能力,数据在写入时无需预知所有字段,查询时按需从 _source 提取。

结果 :一条 FROM view_name 查询,就能融合多个服务的日志,统一规范化它们的 schema,并直接使用那些你当初忘记在 ingest 阶段映射的字段------所有这一切,无需重新索引。


3. 逻辑视图(Logical Views,Tech Preview)

3.1 逻辑视图

逻辑视图可以理解为虚拟索引 (virtual indices)。你通过 _query/view REST API 在集群级别定义一个 ES|QL 查询,并为它赋予一个名称。之后,在任何 FROM 子句中,你可以像使用真实索引一样引用这个名称。
#mermaid-svg-3e4W9lL1xj8XqL4C{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3e4W9lL1xj8XqL4C .error-icon{fill:#552222;}#mermaid-svg-3e4W9lL1xj8XqL4C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3e4W9lL1xj8XqL4C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3e4W9lL1xj8XqL4C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3e4W9lL1xj8XqL4C .marker.cross{stroke:#333333;}#mermaid-svg-3e4W9lL1xj8XqL4C svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3e4W9lL1xj8XqL4C p{margin:0;}#mermaid-svg-3e4W9lL1xj8XqL4C .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster-label text{fill:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster-label span{color:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster-label span p{background-color:transparent;}#mermaid-svg-3e4W9lL1xj8XqL4C .label text,#mermaid-svg-3e4W9lL1xj8XqL4C span{fill:#333;color:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C .node rect,#mermaid-svg-3e4W9lL1xj8XqL4C .node circle,#mermaid-svg-3e4W9lL1xj8XqL4C .node ellipse,#mermaid-svg-3e4W9lL1xj8XqL4C .node polygon,#mermaid-svg-3e4W9lL1xj8XqL4C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3e4W9lL1xj8XqL4C .rough-node .label text,#mermaid-svg-3e4W9lL1xj8XqL4C .node .label text,#mermaid-svg-3e4W9lL1xj8XqL4C .image-shape .label,#mermaid-svg-3e4W9lL1xj8XqL4C .icon-shape .label{text-anchor:middle;}#mermaid-svg-3e4W9lL1xj8XqL4C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3e4W9lL1xj8XqL4C .rough-node .label,#mermaid-svg-3e4W9lL1xj8XqL4C .node .label,#mermaid-svg-3e4W9lL1xj8XqL4C .image-shape .label,#mermaid-svg-3e4W9lL1xj8XqL4C .icon-shape .label{text-align:center;}#mermaid-svg-3e4W9lL1xj8XqL4C .node.clickable{cursor:pointer;}#mermaid-svg-3e4W9lL1xj8XqL4C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3e4W9lL1xj8XqL4C .arrowheadPath{fill:#333333;}#mermaid-svg-3e4W9lL1xj8XqL4C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3e4W9lL1xj8XqL4C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3e4W9lL1xj8XqL4C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3e4W9lL1xj8XqL4C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3e4W9lL1xj8XqL4C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3e4W9lL1xj8XqL4C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster text{fill:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C .cluster span{color:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3e4W9lL1xj8XqL4C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3e4W9lL1xj8XqL4C rect.text{fill:none;stroke-width:0;}#mermaid-svg-3e4W9lL1xj8XqL4C .icon-shape,#mermaid-svg-3e4W9lL1xj8XqL4C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3e4W9lL1xj8XqL4C .icon-shape p,#mermaid-svg-3e4W9lL1xj8XqL4C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3e4W9lL1xj8XqL4C .icon-shape .label rect,#mermaid-svg-3e4W9lL1xj8XqL4C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3e4W9lL1xj8XqL4C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3e4W9lL1xj8XqL4C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3e4W9lL1xj8XqL4C :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 使用
定义
视图定义存储于集群
PUT _query/view/error_triage
FROM svc-gateway-*

| WHERE ...

| KEEP ...
FROM error_triage
| STATS ...

| SORT ...

关键特性

  • 自动更新:修改视图定义后,所有引用该视图的仪表板、告警和临时查询立即生效,无需逐个更新。
  • 可嵌套:视图可以引用其他视图,支持组合复用。
  • 跨集群搜索:视图可跨多个集群定义。
  • 专用 RBAC 权限:可以为视图设置独立的访问控制。

3.2 用法示例

定义视图

esql 复制代码
PUT _query/view/error_triage
{
  "query": """
    FROM svc-gateway-*
    | WHERE http.response.status_code >= 500
    | KEEP @timestamp, http.response.status_code, url.path, source.ip
  """
}

使用视图

esql 复制代码
FROM error_triage
| STATS error_count = COUNT(*) BY url.path
| SORT error_count DESC

在 Kibana Discover 中,自动完成会识别出 error_triage 是一个逻辑视图,并提供语法提示。

3.3 简析

视图本质上是查询定义的存储,并不实际存储任何数据。执行时,Elasticsearch 会将视图内部的查询文本展开,并合并外部查询,生成最终的执行计划。因此,视图不会带来额外的存储开销,但在执行时会有微小的解析开销(可忽略)。


4. 子查询(Subqueries in FROM,Tech Preview)

4.1 为什么需要子查询?

在实际场景中,不同服务的日志往往写入不同的索引,它们的 schema 可能完全不同(例如网关日志有 http.response.status_code,支付日志有 transaction.status,认证日志有 event.outcome)。以往,要合并分析这些数据,要么在 ingest 阶段统一 schema,要么在查询后手动合并------前者笨重,后者低效。

ES|QL 的子查询 提供了组合原语(composition primitive):你可以在单个 FROM 中列出多个子查询,每个子查询针对一个索引(或索引模式)独立处理,然后所有结果以 UNION ALL 语义合并,进入后续管道。

4.2 工作流程关键点

  • 独立过滤 :每个分支有自己的 WHERE,优化器会分别将过滤条件下推到各索引,充分利用索引的统计信息和缓存。
  • 灵活投影EVAL 可以在分支内计算新字段,统一输出结构(例如每个分支都产出 serviceerror_detail 等公共字段)。
  • 性能:各分支并行执行,最终结果合并,整体延迟取决于最慢的分支。

4.3 完整示例

esql 复制代码
FROM
  (FROM svc-gateway-*
   | WHERE http.response.status_code >= 500
   | EVAL service = "gateway",
         error_detail = CONCAT("HTTP ", http.response.status_code::string)
   | KEEP @timestamp, service, error_detail, source.ip),
  (FROM svc-payments-*
   | WHERE transaction.status IN ("failed", "timeout")
   | EVAL service = "payments",
         error_detail = transaction.status
   | KEEP @timestamp, service, error_detail, source.ip),
  (FROM svc-auth-*
   | WHERE event.action == "login" AND event.outcome == "failure"
   | EVAL service = "auth",
         error_detail = CONCAT(event.action, " ", event.outcome)
   | KEEP @timestamp, service, error_detail, source.ip)
| SORT @timestamp DESC
| LIMIT 20

这个查询将三个不同服务的异常事件合并,按时间倒序返回最新的 20 条记录,每条记录都带有统一的 serviceerror_detail 字段。


5. Schema-on-Read:未映射字段与 JSON 提取(Tech Preview)

5.1 传统困境

在 Elasticsearch 中,字段必须在映射(mapping)中定义才能被索引和查询。如果 ingest 时遗漏了某个字段,或者 schema 后期发生变化,通常需要重新索引(reindex)整个数据,这在生产环境中代价高昂。

Schema-on-Read 打破了这一限制。它允许你查询从未被映射的字段 ,直接从 _source(原始 JSON 文档)中按需读取,无需任何事前准备。

5.2 两种访问方式

ES|QL 提供了两个层次的支持:

方式 适用场景 使用方法
SET unmapped_fields="load" 统一开启未映射字段访问,所有未映射字段自动从 _source 读取 在查询开头设置一次,之后即可像普通字段一样使用未映射字段
JSON_EXTRACT 精细化提取,从原始 JSON 字符串或 flattened 类型字段中抽取特定值 EVALWHERE 中使用 JSON_EXTRACT(field, '$.path')

5.3 行为模式

unmapped_fields 支持三种模式:
#mermaid-svg-ChPedDxLUcaDWS0X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ChPedDxLUcaDWS0X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ChPedDxLUcaDWS0X .error-icon{fill:#552222;}#mermaid-svg-ChPedDxLUcaDWS0X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ChPedDxLUcaDWS0X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ChPedDxLUcaDWS0X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ChPedDxLUcaDWS0X .marker.cross{stroke:#333333;}#mermaid-svg-ChPedDxLUcaDWS0X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ChPedDxLUcaDWS0X p{margin:0;}#mermaid-svg-ChPedDxLUcaDWS0X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ChPedDxLUcaDWS0X .cluster-label text{fill:#333;}#mermaid-svg-ChPedDxLUcaDWS0X .cluster-label span{color:#333;}#mermaid-svg-ChPedDxLUcaDWS0X .cluster-label span p{background-color:transparent;}#mermaid-svg-ChPedDxLUcaDWS0X .label text,#mermaid-svg-ChPedDxLUcaDWS0X span{fill:#333;color:#333;}#mermaid-svg-ChPedDxLUcaDWS0X .node rect,#mermaid-svg-ChPedDxLUcaDWS0X .node circle,#mermaid-svg-ChPedDxLUcaDWS0X .node ellipse,#mermaid-svg-ChPedDxLUcaDWS0X .node polygon,#mermaid-svg-ChPedDxLUcaDWS0X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ChPedDxLUcaDWS0X .rough-node .label text,#mermaid-svg-ChPedDxLUcaDWS0X .node .label text,#mermaid-svg-ChPedDxLUcaDWS0X .image-shape .label,#mermaid-svg-ChPedDxLUcaDWS0X .icon-shape .label{text-anchor:middle;}#mermaid-svg-ChPedDxLUcaDWS0X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ChPedDxLUcaDWS0X .rough-node .label,#mermaid-svg-ChPedDxLUcaDWS0X .node .label,#mermaid-svg-ChPedDxLUcaDWS0X .image-shape .label,#mermaid-svg-ChPedDxLUcaDWS0X .icon-shape .label{text-align:center;}#mermaid-svg-ChPedDxLUcaDWS0X .node.clickable{cursor:pointer;}#mermaid-svg-ChPedDxLUcaDWS0X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ChPedDxLUcaDWS0X .arrowheadPath{fill:#333333;}#mermaid-svg-ChPedDxLUcaDWS0X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ChPedDxLUcaDWS0X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ChPedDxLUcaDWS0X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ChPedDxLUcaDWS0X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ChPedDxLUcaDWS0X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ChPedDxLUcaDWS0X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ChPedDxLUcaDWS0X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ChPedDxLUcaDWS0X .cluster text{fill:#333;}#mermaid-svg-ChPedDxLUcaDWS0X .cluster span{color:#333;}#mermaid-svg-ChPedDxLUcaDWS0X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ChPedDxLUcaDWS0X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ChPedDxLUcaDWS0X rect.text{fill:none;stroke-width:0;}#mermaid-svg-ChPedDxLUcaDWS0X .icon-shape,#mermaid-svg-ChPedDxLUcaDWS0X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ChPedDxLUcaDWS0X .icon-shape p,#mermaid-svg-ChPedDxLUcaDWS0X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ChPedDxLUcaDWS0X .icon-shape .label rect,#mermaid-svg-ChPedDxLUcaDWS0X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ChPedDxLUcaDWS0X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ChPedDxLUcaDWS0X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ChPedDxLUcaDWS0X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} unmapped_fields 模式
默认: 忽略未映射字段

(查询中引用会报错)
nullify: 未映射字段显示为 null

(保留列,值为空)
load: 从 _source 读取实际值

(查询时加载)

  • 默认(未设置):引用未映射字段会导致错误,确保 schema 严格。
  • "nullify" :未映射字段在结果中作为 null 列出现,不报错,但也不实际读取。
  • "load"核心模式 ,查询引擎会在需要时从 _source 加载这些字段的值,并缓存以供后续使用。

5.4 使用示例

esql 复制代码
SET unmapped_fields="load";
FROM otel-logs-*
| WHERE log.level IN ("error", "warn")
| STATS errors = COUNT(*), latest = MAX(@timestamp)
    BY service.name, resource.cost_center
| SORT errors DESC

假设 service.nameresource.cost_center 并未在映射中定义,开启 load 后,查询仍然成功执行,ES|QL 会从每个文档的 _source 中提取这些字段的值。

5.5 原理剖析

  • 读取时机 :仅在查询执行期间,当管道需要访问该字段时,才会从 _source 中解析 JSON 并提取值。这带来了灵活性,但会增加查询的 CPU 和 I/O 开销 (因为需要读取和解析 _source)。
  • 缓存:对于多次引用的字段,ES|QL 会在内部缓存提取结果,避免重复解析。
  • 性能考量 :适用于探索性分析低基数查询,对于高频、高性能要求的场景,仍建议将常用字段映射为常规字段(并重新索引)。

JSON_EXTRACT 是更底层的工具,适合从嵌套 JSON 或 flattened 字段中提取特定路径。未来,原生 flattened 字段支持将进一步优化这一流程。


6. 时区支持(Timezone Support,GA)

在时序数据分析中,时区处理一直是个痛点。以前,你需要在每个日期函数中单独指定时区,或者在后处理中转换。现在,SET time_zone 成为全局配置,一次设置,整个查询中的所有日期时间操作自动转换。

6.1 使用方法

esql 复制代码
SET time_zone="America/Los_Angeles";
FROM error_triage
| EVAL hour = DATE_TRUNC(1 hour, @timestamp)
| STATS errors = COUNT(*) BY hour, service
| SORT hour DESC

效果

  • DATE_TRUNC 按洛杉矶时区截断时间。
  • 聚合结果中的 @timestamp 输出会带上时区偏移量(例如 2026-04-09T11:00:00.000-07:00 而非原始的 ...T18:00:00.000Z)。
  • 所有日期比较、聚合、排序都基于同一时区。

6.2 原理解释

ES|QL 在内部将 @timestamp 这样的日期时间字段视为 UTC 时间戳。设置 time_zone 后,所有日期操作在内部先进行时区转换,然后再处理。这避免了每个函数单独传递时区参数的繁琐,也保证了结果的一致性。


7. LIMIT BY:原生分组 Top‑N(Tech Preview)

7.1 痛点

传统 SORT ... LIMIT N 只能返回全局前 N 条记录。如果要"按服务分组,分别返回每个服务中错误数最多的 3 种错误类型",你必须用复杂的子查询或后处理脚本。LIMIT BY 解决了这个原生需求。

7.2 语法与示例

esql 复制代码
FROM error_triage
| STATS cnt = COUNT(*) BY service, error_detail
| SORT cnt DESC
| LIMIT 3 BY service

语义 :按 service 字段分组,对每组内按 cnt 降序排序,取每组前 3 行。数字 3BY 之前。

7.3 内部机制

#mermaid-svg-LGVo8RbIoeQ1oMWe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LGVo8RbIoeQ1oMWe .error-icon{fill:#552222;}#mermaid-svg-LGVo8RbIoeQ1oMWe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LGVo8RbIoeQ1oMWe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .marker.cross{stroke:#333333;}#mermaid-svg-LGVo8RbIoeQ1oMWe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LGVo8RbIoeQ1oMWe p{margin:0;}#mermaid-svg-LGVo8RbIoeQ1oMWe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster-label text{fill:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster-label span{color:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster-label span p{background-color:transparent;}#mermaid-svg-LGVo8RbIoeQ1oMWe .label text,#mermaid-svg-LGVo8RbIoeQ1oMWe span{fill:#333;color:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .node rect,#mermaid-svg-LGVo8RbIoeQ1oMWe .node circle,#mermaid-svg-LGVo8RbIoeQ1oMWe .node ellipse,#mermaid-svg-LGVo8RbIoeQ1oMWe .node polygon,#mermaid-svg-LGVo8RbIoeQ1oMWe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .rough-node .label text,#mermaid-svg-LGVo8RbIoeQ1oMWe .node .label text,#mermaid-svg-LGVo8RbIoeQ1oMWe .image-shape .label,#mermaid-svg-LGVo8RbIoeQ1oMWe .icon-shape .label{text-anchor:middle;}#mermaid-svg-LGVo8RbIoeQ1oMWe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .rough-node .label,#mermaid-svg-LGVo8RbIoeQ1oMWe .node .label,#mermaid-svg-LGVo8RbIoeQ1oMWe .image-shape .label,#mermaid-svg-LGVo8RbIoeQ1oMWe .icon-shape .label{text-align:center;}#mermaid-svg-LGVo8RbIoeQ1oMWe .node.clickable{cursor:pointer;}#mermaid-svg-LGVo8RbIoeQ1oMWe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .arrowheadPath{fill:#333333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LGVo8RbIoeQ1oMWe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LGVo8RbIoeQ1oMWe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LGVo8RbIoeQ1oMWe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster text{fill:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe .cluster span{color:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LGVo8RbIoeQ1oMWe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LGVo8RbIoeQ1oMWe rect.text{fill:none;stroke-width:0;}#mermaid-svg-LGVo8RbIoeQ1oMWe .icon-shape,#mermaid-svg-LGVo8RbIoeQ1oMWe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LGVo8RbIoeQ1oMWe .icon-shape p,#mermaid-svg-LGVo8RbIoeQ1oMWe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LGVo8RbIoeQ1oMWe .icon-shape .label rect,#mermaid-svg-LGVo8RbIoeQ1oMWe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LGVo8RbIoeQ1oMWe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LGVo8RbIoeQ1oMWe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LGVo8RbIoeQ1oMWe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入行
分组操作

(BY service)
每组内部排序

(SORT cnt DESC)
每组保留前 N 条
合并输出

LIMIT BY 是在聚合后、最终输出前的一个管道操作,它利用了分组后的局部排序能力,每个组独立维护一个小根堆,只需扫描一次数据即可完成。


8. 其他重要更新

8.1 FIRST / LAST / EARLIEST / LATEST(GA)

这些聚合函数返回某个分组中,按指定排序字段的最早或最晚记录所对应的值。

函数 参数 说明
FIRST(value, sort_field) 值字段,排序字段 返回排序后第一条记录的 value
LAST(value, sort_field) 值字段,排序字段 返回排序后最后一条记录的 value
EARLIEST(value) 值字段 隐式按 @timestamp 排序,等价于 FIRST(value, @timestamp)
LATEST(value) 值字段 隐式按 @timestamp 排序,等价于 LAST(value, @timestamp)

示例 :按 source.ip 分组,统计每个 IP 的首次和末次失败时间,以及首次失败时的用户名。

esql 复制代码
FROM svc-auth-*
| WHERE event.outcome == "failure"
| STATS first_seen    = FIRST(@timestamp, @timestamp),
        last_seen     = LAST(@timestamp, @timestamp),
        first_user    = EARLIEST(user.name),
        attempts      = COUNT(*)
    BY source.ip
| SORT attempts DESC

8.2 URI_PARTS / USER_AGENT / REGISTERED_DOMAIN(新管道命令)

这三个命令将单个字段展开为多个结构化输出列,极大地简化了解析工作。

命令 输入字段 输出列(示例)
URI_PARTS target = source_field URL 字符串 target.domain, target.path, target.scheme, target.port, target.query
USER_AGENT target = source_field User-Agent 字符串 target.name, target.version, target.os.name, target.device
REGISTERED_DOMAIN target = source_field 域名字符串 target.registered_domain, target.top_level_domain, target.subdomain

用法示例

esql 复制代码
FROM svc-gateway-*
| WHERE http.response.status_code >= 400
| URI_PARTS parts = url.full
| STATS errors = COUNT(*) BY parts.domain, parts.path
| SORT errors DESC

这些命令在内部使用 Lucene 或 Elasticsearch 内置的解析器,效率远高于自定义脚本。


9. Lookup Join 优化(性能增强)

Lookup Join(在 9.1 版本引入)用于将主查询的每一行与一个较小的"查找索引"进行关联,常用于数据丰富(enrichment)。本次发布带来了两项重要优化:

9.1 Lucene 结构复用

  • 问题:每次执行 lookup join,都需要打开查找索引的 doc values 和 TermsEnum 结构,涉及磁盘 I/O。
  • 优化 :现在,对于相同查找索引的重复查询,这些底层结构会被缓存并跨查询复用,避免重复读取。
  • 适用场景:典型的丰富模式------主查询有大量行,查找索引相对较小且被频繁使用。例如,将 IP 地址映射到地理位置信息。

9.2 单关键字连接加速

  • 对于连接键为单个 keyword 字段的最常见情况,ES|QL 使用了一条更快的执行路径,减少了每行连接的开销(例如减少了类型转换和字典查找的代价)。

这两项优化使得 lookup join 在生产环境中更具实用性,尤其适合高吞吐量的数据丰富需求。


总结

功能 状态 核心价值
逻辑视图 Tech Preview 查询复用,一处定义,全局生效
子查询(FROM 中) Tech Preview 合并不同 schema 的索引,各分支独立处理
Schema-on-Read(未映射字段) Tech Preview 无需 reindex,查询时按需读取 _source
时区支持 GA 全局时区设置,简化日期时间处理
LIMIT BY Tech Preview 原生分组 Top‑N,无需后处理
FIRST / LAST / EARLIEST / LATEST GA 分组内极值关联取值
URI_PARTS / USER_AGENT / REGISTERED_DOMAIN 新命令 解析结构化字段为多列
Lookup Join 优化 性能提升 缓存复用 + 单键加速,更适合丰富场景
VALUES / MV_EXPAND / FORK GA 多值字段操作与并行执行分支
SPARKLINE / MV_UNION / MV_DIFFERENCE / MV_INTERSECTS Tech Preview 内置迷你图与集合运算

如何用

  • 所有 Tech Preview 功能已包含在 Elasticsearch 的最新版本中(Serverless 中视图暂不可用)。
  • 可以在 Kibana 的 Dev Tools 或 Discover 中直接尝试。

重要提示:Tech Preview 功能可能在未来版本中变更,且不享受 GA 功能的 SLA 支持。功能的上线时间由 Elastic 自行决定。


通过上述能力,ES|QL 正在从一个单纯的查询语言,演变为一个完整的数据访问与组合层。逻辑视图、子查询和 Schema-on-Read 三驾马车,让数据建模变得更加动态、灵活,也大大降低了运维成本。