Elasticsearch ES|QL 新特性学习:视图、子查询与读时模式

曾经为索引中漏掉的字段而头疼?是否曾为多个服务索引结构不一而不得不写复杂的应用层代码?是否梦想过把一段复杂的查询逻辑像"视图"一样到处复用?Elasticsearch 的 ES|QL 三大能力------

逻辑视图(Logical Views)

FROM 子查询(Subqueries)

*读时模式(Schema-on-Read)


一、一次查询,统揽全局

在 9.x 版本中,ES|QL(Elasticsearch Query Language)迎来了自诞生以来最大规模的数据访问能力升级。这次升级直击三个核心痛点:

  • 映射遗漏:数据已经索引,但忘记在 mapping 中定义某些字段,难道要重新索引?不必了。
  • 索引异构:不同微服务的日志索引结构各异,如何在一个查询中无缝合并分析?
  • 逻辑复用:一段精心调优的过滤和转换逻辑,能否像数据库视图一样被所有仪表盘和告警引用?

新版本给出的答案是三个环环相扣的特性:

特性 解决痛点 状态
逻辑视图(Logical Views) 查询逻辑复用、统一管理 Tech Preview
FROM 子查询(Subqueries) 合并异构索引,各自独立管道 Tech Preview
读时模式(Schema-on-Read) 查询未映射字段,无需重新索引 Tech Preview

与此同时,时区支持(Timezone) 正式 GA,LIMIT BY 原生支持分组 Top-N,LOOKUP JOIN 性能大幅优化,还有一批新函数和管道命令加入。本文会带你逐一领略,并用 Mermaid 流程图 直观展示内部运作机制。


二、三大能力协同工作

先看一张全景图,理解 Views、Subqueries 和 Schema-on-Read 是如何叠加起来产生化学反应的:
#mermaid-svg-hvQzNxwos4g5Jm2m{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-hvQzNxwos4g5Jm2m .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hvQzNxwos4g5Jm2m .error-icon{fill:#552222;}#mermaid-svg-hvQzNxwos4g5Jm2m .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hvQzNxwos4g5Jm2m .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hvQzNxwos4g5Jm2m .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hvQzNxwos4g5Jm2m .marker.cross{stroke:#333333;}#mermaid-svg-hvQzNxwos4g5Jm2m svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hvQzNxwos4g5Jm2m p{margin:0;}#mermaid-svg-hvQzNxwos4g5Jm2m .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster-label text{fill:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster-label span{color:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster-label span p{background-color:transparent;}#mermaid-svg-hvQzNxwos4g5Jm2m .label text,#mermaid-svg-hvQzNxwos4g5Jm2m span{fill:#333;color:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m .node rect,#mermaid-svg-hvQzNxwos4g5Jm2m .node circle,#mermaid-svg-hvQzNxwos4g5Jm2m .node ellipse,#mermaid-svg-hvQzNxwos4g5Jm2m .node polygon,#mermaid-svg-hvQzNxwos4g5Jm2m .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hvQzNxwos4g5Jm2m .rough-node .label text,#mermaid-svg-hvQzNxwos4g5Jm2m .node .label text,#mermaid-svg-hvQzNxwos4g5Jm2m .image-shape .label,#mermaid-svg-hvQzNxwos4g5Jm2m .icon-shape .label{text-anchor:middle;}#mermaid-svg-hvQzNxwos4g5Jm2m .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hvQzNxwos4g5Jm2m .rough-node .label,#mermaid-svg-hvQzNxwos4g5Jm2m .node .label,#mermaid-svg-hvQzNxwos4g5Jm2m .image-shape .label,#mermaid-svg-hvQzNxwos4g5Jm2m .icon-shape .label{text-align:center;}#mermaid-svg-hvQzNxwos4g5Jm2m .node.clickable{cursor:pointer;}#mermaid-svg-hvQzNxwos4g5Jm2m .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hvQzNxwos4g5Jm2m .arrowheadPath{fill:#333333;}#mermaid-svg-hvQzNxwos4g5Jm2m .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hvQzNxwos4g5Jm2m .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hvQzNxwos4g5Jm2m .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hvQzNxwos4g5Jm2m .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hvQzNxwos4g5Jm2m .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hvQzNxwos4g5Jm2m .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster text{fill:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m .cluster span{color:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m 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-hvQzNxwos4g5Jm2m .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hvQzNxwos4g5Jm2m rect.text{fill:none;stroke-width:0;}#mermaid-svg-hvQzNxwos4g5Jm2m .icon-shape,#mermaid-svg-hvQzNxwos4g5Jm2m .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hvQzNxwos4g5Jm2m .icon-shape p,#mermaid-svg-hvQzNxwos4g5Jm2m .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hvQzNxwos4g5Jm2m .icon-shape .label rect,#mermaid-svg-hvQzNxwos4g5Jm2m .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hvQzNxwos4g5Jm2m .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hvQzNxwos4g5Jm2m .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hvQzNxwos4g5Jm2m :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Users
读时模式
SET unmapped_fields='load'
字段从 _source 动态提取
FROM 子查询管道
索引A: 网关日志

WHERE status>=500

EVAL service='gateway'
索引B: 支付日志

WHERE transaction.status in ...

EVAL service='payments'
索引C: 认证日志

WHERE event.action='login'...

EVAL service='auth'
UNION ALL 合并
逻辑视图 (error_triage)
定义查询
存储于集群状态
Kibana Dashboard
Alerting
Ad-hoc Query
统一结果集

  • 视图 将复杂的子查询封装为一个名字,消费者只需 FROM error_triage
  • 子查询 为每个索引独立定义过滤、计算和投影,最后以 UNION ALL 合并。
  • 读时模式 让子查询中的字段即使从未映射,也能从 _source 中按需加载。

三者结合,你就能写出这样的查询:

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

error_triage 的背后可能同时查询了网关、支付、认证三个服务的索引,并且用到了未映射的 url.path 字段------所有复杂性都被屏蔽在视图内部。


三、逻辑视图(Logical Views)------一次定义,处处复用

3.1 什么是逻辑视图?

逻辑视图本质上是一个命名的 ES|QL 查询,存储在 Elasticsearch 集群的元数据中。 它不存储数据,只存储查询定义。当你在 FROM 子句中引用视图名时,ES|QL 引擎会将其替换为视图对应的查询体,然后执行。

这就像数据库中的 CREATE VIEW,但更轻量,并且完全与 ES|QL 的管道语法融合。

3.2 如何创建和使用?

通过 _query/view API 创建:

http 复制代码
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
  """
}

然后你就可以在任何 ES|QL 查询中像使用普通索引一样使用它:

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

3.3 原理与实现

#mermaid-svg-X9jsLfOUL01qC96f{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-X9jsLfOUL01qC96f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-X9jsLfOUL01qC96f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-X9jsLfOUL01qC96f .error-icon{fill:#552222;}#mermaid-svg-X9jsLfOUL01qC96f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-X9jsLfOUL01qC96f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-X9jsLfOUL01qC96f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-X9jsLfOUL01qC96f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-X9jsLfOUL01qC96f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-X9jsLfOUL01qC96f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-X9jsLfOUL01qC96f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-X9jsLfOUL01qC96f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-X9jsLfOUL01qC96f .marker.cross{stroke:#333333;}#mermaid-svg-X9jsLfOUL01qC96f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-X9jsLfOUL01qC96f p{margin:0;}#mermaid-svg-X9jsLfOUL01qC96f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-X9jsLfOUL01qC96f .cluster-label text{fill:#333;}#mermaid-svg-X9jsLfOUL01qC96f .cluster-label span{color:#333;}#mermaid-svg-X9jsLfOUL01qC96f .cluster-label span p{background-color:transparent;}#mermaid-svg-X9jsLfOUL01qC96f .label text,#mermaid-svg-X9jsLfOUL01qC96f span{fill:#333;color:#333;}#mermaid-svg-X9jsLfOUL01qC96f .node rect,#mermaid-svg-X9jsLfOUL01qC96f .node circle,#mermaid-svg-X9jsLfOUL01qC96f .node ellipse,#mermaid-svg-X9jsLfOUL01qC96f .node polygon,#mermaid-svg-X9jsLfOUL01qC96f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-X9jsLfOUL01qC96f .rough-node .label text,#mermaid-svg-X9jsLfOUL01qC96f .node .label text,#mermaid-svg-X9jsLfOUL01qC96f .image-shape .label,#mermaid-svg-X9jsLfOUL01qC96f .icon-shape .label{text-anchor:middle;}#mermaid-svg-X9jsLfOUL01qC96f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-X9jsLfOUL01qC96f .rough-node .label,#mermaid-svg-X9jsLfOUL01qC96f .node .label,#mermaid-svg-X9jsLfOUL01qC96f .image-shape .label,#mermaid-svg-X9jsLfOUL01qC96f .icon-shape .label{text-align:center;}#mermaid-svg-X9jsLfOUL01qC96f .node.clickable{cursor:pointer;}#mermaid-svg-X9jsLfOUL01qC96f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-X9jsLfOUL01qC96f .arrowheadPath{fill:#333333;}#mermaid-svg-X9jsLfOUL01qC96f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-X9jsLfOUL01qC96f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-X9jsLfOUL01qC96f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X9jsLfOUL01qC96f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-X9jsLfOUL01qC96f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X9jsLfOUL01qC96f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-X9jsLfOUL01qC96f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-X9jsLfOUL01qC96f .cluster text{fill:#333;}#mermaid-svg-X9jsLfOUL01qC96f .cluster span{color:#333;}#mermaid-svg-X9jsLfOUL01qC96f 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-X9jsLfOUL01qC96f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-X9jsLfOUL01qC96f rect.text{fill:none;stroke-width:0;}#mermaid-svg-X9jsLfOUL01qC96f .icon-shape,#mermaid-svg-X9jsLfOUL01qC96f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-X9jsLfOUL01qC96f .icon-shape p,#mermaid-svg-X9jsLfOUL01qC96f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-X9jsLfOUL01qC96f .icon-shape .label rect,#mermaid-svg-X9jsLfOUL01qC96f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-X9jsLfOUL01qC96f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-X9jsLfOUL01qC96f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-X9jsLfOUL01qC96f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户查询: FROM error_triage ...
解析器识别视图名
查询集群状态中的视图定义
将视图查询体展开到 FROM 位置
优化器合并过滤条件,推送到底层索引
执行展开后的完整查询

  • 存储:视图定义保存在集群状态(cluster state)中,由主节点维护,可跨集群搜索。
  • 权限:支持独立的 RBAC 权限,可以控制哪些用户或角色能使用某个视图。
  • 嵌套:视图可以引用其他视图,但要注意避免循环依赖。
  • 自动更新:修改视图定义后,所有引用它的仪表盘、告警、查询立即生效,无需重新部署。

3.4 使用场景

  • 统一错误排查入口:将多个服务的错误日志合并成一个视图,SRE 团队只需查询这一个视图。
  • 安全脱敏 :在视图中 DROP 掉敏感字段,再授权给非特权用户。
  • 复杂业务指标:如"最近一小时活跃用户"的复杂计算,封装成视图供多个看板使用。

四、FROM 子查询(Subqueries)------异构索引的融合器

4.1 子查询

在生产环境中,不同微服务的日志往往存放在不同的索引中,它们的字段定义可能完全不同。比如:

  • 网关日志有 http.response.status_code
  • 支付日志有 transaction.status
  • 认证日志有 event.actionevent.outcome

要把它们合并成一个统一的"错误事件"列表,过去你需要在应用层查三次再合并,或者用 Elasticsearch 的 multi-index 但只能共享同一个过滤条件。现在,FROM 子查询允许你对每个索引独立编写管道 ,最后自动做 UNION ALL

4.2 语法与示例

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

每个子查询都是一个独立的 ES|QL 管道,它们以逗号分隔,最终合并成一个结果流。后续的 SORTLIMIT 作用在整个合并结果上。

4.3 内部执行流程

#mermaid-svg-0AKvUtEshqEbpTar{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-0AKvUtEshqEbpTar .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0AKvUtEshqEbpTar .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0AKvUtEshqEbpTar .error-icon{fill:#552222;}#mermaid-svg-0AKvUtEshqEbpTar .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0AKvUtEshqEbpTar .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0AKvUtEshqEbpTar .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0AKvUtEshqEbpTar .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0AKvUtEshqEbpTar .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0AKvUtEshqEbpTar .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0AKvUtEshqEbpTar .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0AKvUtEshqEbpTar .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0AKvUtEshqEbpTar .marker.cross{stroke:#333333;}#mermaid-svg-0AKvUtEshqEbpTar svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0AKvUtEshqEbpTar p{margin:0;}#mermaid-svg-0AKvUtEshqEbpTar .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0AKvUtEshqEbpTar .cluster-label text{fill:#333;}#mermaid-svg-0AKvUtEshqEbpTar .cluster-label span{color:#333;}#mermaid-svg-0AKvUtEshqEbpTar .cluster-label span p{background-color:transparent;}#mermaid-svg-0AKvUtEshqEbpTar .label text,#mermaid-svg-0AKvUtEshqEbpTar span{fill:#333;color:#333;}#mermaid-svg-0AKvUtEshqEbpTar .node rect,#mermaid-svg-0AKvUtEshqEbpTar .node circle,#mermaid-svg-0AKvUtEshqEbpTar .node ellipse,#mermaid-svg-0AKvUtEshqEbpTar .node polygon,#mermaid-svg-0AKvUtEshqEbpTar .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0AKvUtEshqEbpTar .rough-node .label text,#mermaid-svg-0AKvUtEshqEbpTar .node .label text,#mermaid-svg-0AKvUtEshqEbpTar .image-shape .label,#mermaid-svg-0AKvUtEshqEbpTar .icon-shape .label{text-anchor:middle;}#mermaid-svg-0AKvUtEshqEbpTar .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0AKvUtEshqEbpTar .rough-node .label,#mermaid-svg-0AKvUtEshqEbpTar .node .label,#mermaid-svg-0AKvUtEshqEbpTar .image-shape .label,#mermaid-svg-0AKvUtEshqEbpTar .icon-shape .label{text-align:center;}#mermaid-svg-0AKvUtEshqEbpTar .node.clickable{cursor:pointer;}#mermaid-svg-0AKvUtEshqEbpTar .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0AKvUtEshqEbpTar .arrowheadPath{fill:#333333;}#mermaid-svg-0AKvUtEshqEbpTar .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0AKvUtEshqEbpTar .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0AKvUtEshqEbpTar .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0AKvUtEshqEbpTar .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0AKvUtEshqEbpTar .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0AKvUtEshqEbpTar .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0AKvUtEshqEbpTar .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0AKvUtEshqEbpTar .cluster text{fill:#333;}#mermaid-svg-0AKvUtEshqEbpTar .cluster span{color:#333;}#mermaid-svg-0AKvUtEshqEbpTar 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-0AKvUtEshqEbpTar .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0AKvUtEshqEbpTar rect.text{fill:none;stroke-width:0;}#mermaid-svg-0AKvUtEshqEbpTar .icon-shape,#mermaid-svg-0AKvUtEshqEbpTar .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0AKvUtEshqEbpTar .icon-shape p,#mermaid-svg-0AKvUtEshqEbpTar .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0AKvUtEshqEbpTar .icon-shape .label rect,#mermaid-svg-0AKvUtEshqEbpTar .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0AKvUtEshqEbpTar .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0AKvUtEshqEbpTar .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0AKvUtEshqEbpTar :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Branches
子查询1: 网关索引
WHERE/EVAL/KEEP
子查询2: 支付索引
WHERE/EVAL/KEEP
子查询3: 认证索引
WHERE/EVAL/KEEP
UNION ALL 合并
全局管道: SORT / LIMIT / STATS
最终结果

关键优化点

  • 过滤器下推 :优化器会将每个子查询中的 WHERE 条件推送到对应的索引分片,充分利用索引过滤,减少扫描数据量。
  • 并行执行:各子查询可以并行扫描不同的索引,最后在协调节点合并。
  • 独立字段解析:每个子查询有自己的字段解析上下文,因此字段名可以不同,甚至同名字段类型不同时会在合并时做类型提升(如 integer 和 long 合并为 long)。

4.4 与视图的结合

子查询是视图的"建筑材料"------你可以定义一个视图,其查询体就是一段复杂的子查询组合,这样消费者完全感知不到底层的索引异构性。


五、读时模式(Schema-on-Read)------让遗漏的映射不再是噩梦

5.1 痛点回顾

Elasticsearch 是 schema-on-write 模式,索引数据前需要定义 mapping。如果后来发现漏了某个字段,传统做法是:

  1. 更新 mapping(但已索引的历史数据不会自动填充该字段)。
  2. 使用 _reindex 将数据重新索引到新映射,对大数据量耗时且昂贵。

读时模式 打破了这一限制:你可以在查询时动态从 _source 中提取任意字段,即使它们从未出现在 mapping 中。

5.2 两种方式

方式一:SET unmapped_fields="load"(战略级抽象)
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

行为说明 :当这个开关开启后,查询中引用的任何字段,如果不在 mapping 中,ES|QL 会自动从 _source 中提取对应值。这让你可以"先上车后补票",先索引原始 JSON,后续随时查询任意嵌套字段。

方式二:JSON_EXTRACT(手术刀式精确提取)
esql 复制代码
FROM my-index
| EVAL user_id = JSON_EXTRACT(json_field, "$.user.id")
| WHERE user_id = 123

JSON_EXTRACT 更底层,适合从存储为字符串的 JSON 或 flattened 字段中提取子字段。但 SET unmapped_fields="load" 使用起来更简洁,适合大多数场景。

5.3 内部原理:从 _source 动态加载

#mermaid-svg-fsZqNYHdvwvUEOAj{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-fsZqNYHdvwvUEOAj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fsZqNYHdvwvUEOAj .error-icon{fill:#552222;}#mermaid-svg-fsZqNYHdvwvUEOAj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fsZqNYHdvwvUEOAj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fsZqNYHdvwvUEOAj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fsZqNYHdvwvUEOAj .marker.cross{stroke:#333333;}#mermaid-svg-fsZqNYHdvwvUEOAj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fsZqNYHdvwvUEOAj p{margin:0;}#mermaid-svg-fsZqNYHdvwvUEOAj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster-label text{fill:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster-label span{color:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster-label span p{background-color:transparent;}#mermaid-svg-fsZqNYHdvwvUEOAj .label text,#mermaid-svg-fsZqNYHdvwvUEOAj span{fill:#333;color:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj .node rect,#mermaid-svg-fsZqNYHdvwvUEOAj .node circle,#mermaid-svg-fsZqNYHdvwvUEOAj .node ellipse,#mermaid-svg-fsZqNYHdvwvUEOAj .node polygon,#mermaid-svg-fsZqNYHdvwvUEOAj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fsZqNYHdvwvUEOAj .rough-node .label text,#mermaid-svg-fsZqNYHdvwvUEOAj .node .label text,#mermaid-svg-fsZqNYHdvwvUEOAj .image-shape .label,#mermaid-svg-fsZqNYHdvwvUEOAj .icon-shape .label{text-anchor:middle;}#mermaid-svg-fsZqNYHdvwvUEOAj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fsZqNYHdvwvUEOAj .rough-node .label,#mermaid-svg-fsZqNYHdvwvUEOAj .node .label,#mermaid-svg-fsZqNYHdvwvUEOAj .image-shape .label,#mermaid-svg-fsZqNYHdvwvUEOAj .icon-shape .label{text-align:center;}#mermaid-svg-fsZqNYHdvwvUEOAj .node.clickable{cursor:pointer;}#mermaid-svg-fsZqNYHdvwvUEOAj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fsZqNYHdvwvUEOAj .arrowheadPath{fill:#333333;}#mermaid-svg-fsZqNYHdvwvUEOAj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fsZqNYHdvwvUEOAj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fsZqNYHdvwvUEOAj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fsZqNYHdvwvUEOAj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fsZqNYHdvwvUEOAj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fsZqNYHdvwvUEOAj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster text{fill:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj .cluster span{color:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj 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-fsZqNYHdvwvUEOAj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fsZqNYHdvwvUEOAj rect.text{fill:none;stroke-width:0;}#mermaid-svg-fsZqNYHdvwvUEOAj .icon-shape,#mermaid-svg-fsZqNYHdvwvUEOAj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fsZqNYHdvwvUEOAj .icon-shape p,#mermaid-svg-fsZqNYHdvwvUEOAj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fsZqNYHdvwvUEOAj .icon-shape .label rect,#mermaid-svg-fsZqNYHdvwvUEOAj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fsZqNYHdvwvUEOAj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fsZqNYHdvwvUEOAj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fsZqNYHdvwvUEOAj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

nullify
load
查询引用字段 X
字段在 mapping 中?
正常从列存(doc_values)读取
unmapped_fields 设置?
返回 null
从 _source 解析该字段
缓存在查询上下文中
返回字段值

  • unmapped_fields="load" 时,ES|QL 解析器会检查每个引用字段是否在映射中,若不在,则标记为"需要从 _source 加载"。
  • 执行阶段,对于每个文档,系统只从 _source 中提取查询中实际用到的未映射字段,而不是加载整个 _source,因此性能开销相对可控。
  • 提取后的值会参与后续所有管道操作,和普通映射字段的行为完全一致。

注意 :读时模式目前是 Tech Preview,建议在测试环境充分验证性能表现。对于高频查询,后续路线图中会提供"原生扁平字段支持",进一步优化性能。

5.4 使用建议

  • 适用场景:日志分析、探索式查询、字段经常变动的业务。
  • 不适用场景 :对性能要求极苛刻的实时核心交易查询(因为 _source 读取比列存慢)。
  • 最佳实践:读时模式和传统映射可以共存,你可以只针对少数漏配字段使用读时模式,大部分字段仍用映射。

六、时区支持(GA)------让时间真正属于你

之前 ES|QL 中的日期操作默认使用 UTC 时间,你必须在应用层做时区转换。现在 SET time_zone 正式 GA,一次设置,所有时间函数均按指定时区处理。

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

原理 :时区信息会传递给 DATE_TRUNC、日期直方图聚合以及最终时间戳的格式化输出。底层存储的 @timestamp 依然是 UTC 毫秒值,但在计算和展示时通过时区偏移量调整,无需修改数据。

支持所有 IANA 时区,如 America/New_YorkEurope/London 等。


七、LIMIT BY------原生分组 Top-N

以往要获取"每个服务的前3个错误类型",你需要先全局排序,再用应用代码或复杂的脚本过滤。现在一条命令搞定:

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

执行逻辑
#mermaid-svg-ALpNcIh4vryVFSHv{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-ALpNcIh4vryVFSHv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ALpNcIh4vryVFSHv .error-icon{fill:#552222;}#mermaid-svg-ALpNcIh4vryVFSHv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ALpNcIh4vryVFSHv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ALpNcIh4vryVFSHv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ALpNcIh4vryVFSHv .marker.cross{stroke:#333333;}#mermaid-svg-ALpNcIh4vryVFSHv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ALpNcIh4vryVFSHv p{margin:0;}#mermaid-svg-ALpNcIh4vryVFSHv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ALpNcIh4vryVFSHv .cluster-label text{fill:#333;}#mermaid-svg-ALpNcIh4vryVFSHv .cluster-label span{color:#333;}#mermaid-svg-ALpNcIh4vryVFSHv .cluster-label span p{background-color:transparent;}#mermaid-svg-ALpNcIh4vryVFSHv .label text,#mermaid-svg-ALpNcIh4vryVFSHv span{fill:#333;color:#333;}#mermaid-svg-ALpNcIh4vryVFSHv .node rect,#mermaid-svg-ALpNcIh4vryVFSHv .node circle,#mermaid-svg-ALpNcIh4vryVFSHv .node ellipse,#mermaid-svg-ALpNcIh4vryVFSHv .node polygon,#mermaid-svg-ALpNcIh4vryVFSHv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ALpNcIh4vryVFSHv .rough-node .label text,#mermaid-svg-ALpNcIh4vryVFSHv .node .label text,#mermaid-svg-ALpNcIh4vryVFSHv .image-shape .label,#mermaid-svg-ALpNcIh4vryVFSHv .icon-shape .label{text-anchor:middle;}#mermaid-svg-ALpNcIh4vryVFSHv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ALpNcIh4vryVFSHv .rough-node .label,#mermaid-svg-ALpNcIh4vryVFSHv .node .label,#mermaid-svg-ALpNcIh4vryVFSHv .image-shape .label,#mermaid-svg-ALpNcIh4vryVFSHv .icon-shape .label{text-align:center;}#mermaid-svg-ALpNcIh4vryVFSHv .node.clickable{cursor:pointer;}#mermaid-svg-ALpNcIh4vryVFSHv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ALpNcIh4vryVFSHv .arrowheadPath{fill:#333333;}#mermaid-svg-ALpNcIh4vryVFSHv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ALpNcIh4vryVFSHv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ALpNcIh4vryVFSHv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ALpNcIh4vryVFSHv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ALpNcIh4vryVFSHv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ALpNcIh4vryVFSHv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ALpNcIh4vryVFSHv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ALpNcIh4vryVFSHv .cluster text{fill:#333;}#mermaid-svg-ALpNcIh4vryVFSHv .cluster span{color:#333;}#mermaid-svg-ALpNcIh4vryVFSHv 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-ALpNcIh4vryVFSHv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ALpNcIh4vryVFSHv rect.text{fill:none;stroke-width:0;}#mermaid-svg-ALpNcIh4vryVFSHv .icon-shape,#mermaid-svg-ALpNcIh4vryVFSHv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ALpNcIh4vryVFSHv .icon-shape p,#mermaid-svg-ALpNcIh4vryVFSHv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ALpNcIh4vryVFSHv .icon-shape .label rect,#mermaid-svg-ALpNcIh4vryVFSHv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ALpNcIh4vryVFSHv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ALpNcIh4vryVFSHv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ALpNcIh4vryVFSHv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分组聚合结果
按 service 分组
每组内取 cnt 最大的 3 行
返回每组 TopN

  • LIMIT N BY field 会在每个 field 的取值组内保留前 N 条记录(基于前面的 SORT 顺序)。
  • 如果某个分组不足 N 条,则返回该组所有记录。
  • 该操作在协调节点完成,适合分组数不太多(如几百以内)的场景。

八、FIRST/LAST/EARLIEST/LATEST(GA)------时间序列取值更简单

在时间序列分析中,经常需要取一组记录中最早或最晚的某个字段值。

  • FIRST(value_field, sort_field):按 sort_field 升序排列后,取第一条记录的 value_field
  • LAST(value_field, sort_field):类似,取最后一条。
  • EARLIEST(value_field):等同于 FIRST(value_field, @timestamp),默认按时间戳排序。
  • LATEST(value_field):等同于 LAST(value_field, @timestamp)

示例:

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

这会按 source.ip 分组,找出每个 IP 首次和最后一次失败的时间,以及首次失败的用户名。


九、URI_PARTSUSER_AGENTREGISTERED_DOMAIN------结构化解析管道

这三个新管道命令将常见的字符串解析工作集成到 ES|QL 中,省去在应用层解析的麻烦。

URI_PARTS

将 URL 拆解为协议、域名、路径、端口等:

esql 复制代码
FROM svc-gateway-*
| URI_PARTS parts = url.full
| STATS errors = COUNT(*) BY parts.domain, parts.path

输出字段:parts.schemeparts.domainparts.pathparts.portparts.query 等。

USER_AGENT

解析 User-Agent 字符串,得到浏览器、操作系统、设备信息:

esql 复制代码
FROM svc-auth-*
| USER_AGENT ua = user_agent.original
| STATS cnt = COUNT(*) BY ua.name, ua.version, ua.os.name

REGISTERED_DOMAIN

从域名中提取注册域和顶级域,用于分析访问来源的域名分布:

esql 复制代码
FROM access-logs
| REGISTERED_DOMAIN rd = hostname
| STATS visits = COUNT(*) BY rd.registered_domain, rd.top_level_domain

十、LOOKUP JOIN 性能优化

ES|QL 在 9.1 引入了 LOOKUP JOIN,用于将主查询与一个较小的索引做左连接,实现数据富化。本次升级带来了两项性能优化:

  1. Lucene 结构复用

    当多次执行相同查询(不同参数)查询同一个 lookup 索引时,系统会缓存该索引的 doc_valuesTermsEnum 数据结构,避免重复打开文件和构建字典。这对典型场景------如"每条日志查找用户信息表"------效果显著,因为 lookup 索引通常远小于主表。

  2. 单关键字 join 快速路径

    如果 join 条件是一个单字段(最常见的情况),执行器会采用更轻量的哈希查找,减少每行处理开销。

优化后,LOOKUP JOIN 在高并发富化场景下性能提升可达数倍。


本地化部署与实战建议

Elasticsearch 的本地化部署需要注意:

  • 时区设置 :建议默认使用 Asia/Shanghai,避免每次查询手动设置。
  • 索引命名规范 :统一采用 服务名-日期 模式,便于子查询使用通配符(如 svc-*-*)。
  • 读时模式慎用 :尽管方便,但频繁从 _source 读取大字段可能拖慢查询。建议先用 SET unmapped_fields="nullify" 测试查询逻辑,确认只涉及少量未映射字段时再启用 load
  • 视图权限控制:利用视图的 RBAC,可以为不同团队提供定制化的数据视角,减少误操作风险。
  • Kibana 集成:在 Kibana 的 ES|QL 实验室中尝试新语法,逐步迁移现有查询到视图和子查询模式。

结语

Elasticsearch ES|QL 的这次更新,不仅仅是增加了几个关键字,而是从根本上改变了我们与数据交互的方式:

  • 视图 让逻辑复用成为一等公民;
  • 子查询 让异构数据源融合变得优雅;
  • 读时模式 让 schema 演进不再是负担;
  • 时区、LIMIT BY、LOOKUP 优化 则让日常查询更加顺手。

本文基于 Elasticsearch 9.x 版本,所有特性 API 以官方最新文档为准。