Elasticsearch ES|QL 将全文搜索带到你从未建立索引的数据中

作者:来自 Elastic Kevin CorcoranIoana Tagirta

MATCHTO_TEXT 将全文搜索能力带到你从未建立索引的数据中。在 ES|QL 中,你可以对计算列、未映射字段以及联邦数据源执行全文搜索。

ES|QL MATCH 现在可以对你从未建立索引的数据执行全文搜索。无论是计算列、未映射字段、动态拼接生成的字符串,还是存储在 S3 中的联邦数据,都可以进行全文搜索。新的 TO_TEXT 函数会告诉 ES|QL 将任意字符串视为可分析(analyzable)的文本,因此 MATCH 可以对仅在查询生命周期内存在的值执行分词、大小写归一化以及词项匹配。这超越了大多数查询引擎针对未建立索引字符串所提供的 LIKERLIKE 模式匹配,而是真正的全文分析功能。该功能现已在 Elastic Cloud Serverless 中提供,并作为 Elasticsearch 9.5 的技术预览功能发布。

MATCHTO_TEXT 如何让任意 ES|QL 表达式支持全文搜索

让我们从一个在 Elasticsearch 9.4 中无法实现的查询开始,该查询使用了 EVAL 命令

scss 复制代码
`

1.  FROM cooking_blog
2.  | EVAL summary = TO_TEXT(CONCAT(title, description))
3.  | WHERE MATCH(summary, "pancakes")
4.  | KEEP title, author

`AI写代码

在这个示例中,summary 没有映射(mapping),也没有分析器(analyzer)配置。它也没有关联任何倒排索引(inverted index)。它仅在当前查询执行期间存在,但现在你依然可以对它执行全文搜索。这一能力由两项新增功能共同实现。

首先,[MATCH](https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/search-functions/match "MATCH") 的第一个参数现在可以接受任意表达式,而不仅仅是已映射字段。这包括:

  • EVAL 生成的列;

  • 内联使用的函数返回值;

  • 直接从原始文档读取的未映射字段。

此外,所有 MATCH 通常支持的数据类型,在这种新用法中同样受支持。

第二项新增功能是 [TO_TEXT](https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/type-conversion-functions/to_text "TO_TEXT") 函数,这是 ES|QL 第一个输出 text 类型的转换函数。

此前,text 类型的列只能来自已建立索引的映射字段,而所有由 ES|QL 表达式生成的字符串都会被视为 keyword 类型,而不是 text 类型。

这种区别非常重要,因为 MATCH 对两者的处理方式不同:

  • text 类型会进行文本分析(analysis);

  • keyword 类型则进行精确匹配,这与针对已建立索引的 keyword 字段执行 MATCH 查询时最终重写为 term 查询的行为一致。

TO_TEXT(x) 就是在告诉 ES|QL:"请把这个字符串当作全文文本来处理。"

该功能作为 Elasticsearch 9.5 的技术预览发布,因此目前仍存在一些限制:

  • 目前仅支持过滤(filtering)。针对表达式执行的 MATCH 查询暂时不会参与相关性评分(relevance score),只有针对已建立索引字段的匹配才会影响评分。

  • 在表达式上执行 MATCH 时,目前还不支持 fuzziness 等查询选项。

  • 运行时文本(runtime text)统一使用标准分析器(standard analyzer)进行分析,目前尚不能自定义配置。

Elastic 正在持续完善这些限制,并将在后续版本中逐步支持相关能力。

为什么在 ES|QL 中使用全文搜索,而不是 LIKERLIKE

ES|QL 已经提供了两种无需索引即可搜索字符串的方式:LIKE(通配符模式匹配)和 RLIKE(正则表达式)。它们都可以作用于任意字符串表达式,因此很自然会问:MATCH 又带来了什么?

答案是文本分析(analysis------ 一种更高级的搜索方式,它利用词干提取(stemming)、同义词(synonyms)以及停用词(stopwords)处理等技术来执行全文搜索。

LIKE 本质上只是简单的子字符串匹配,并不了解字符串中的单词。

例如,你想查找与狐狸(fox)相关的日志:

sql 复制代码
`

1.  FROM app_logs
2.  | WHERE message LIKE "*fox*"

`AI写代码

这个查询会遗漏 "Fox spotted near the henhouse",因为大小写不同;却会匹配 "Outfoxed by the competition",尽管它并不是在讨论狐狸。

也就是说,它在两个方向上都会出现问题:

  • 因大小写导致漏匹配(false negatives);

  • 因子字符串出现在其他单词中导致误匹配(false positives)。

正则表达式可以解决大小写问题,但处理单词边界会迅速变得复杂。例如:

css 复制代码
`

1.  FROM app_logs
2.  | WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"

`AI写代码

即便如此,这个表达式仍然不够完善。

它无法匹配句末跟着 !?"fox",也没有考虑制表符、引号或括号等情况。每修复一种情况,正则表达式都会变得更长,而下一位阅读该查询的人还得花时间弄清楚它到底在做什么。

MATCH 则彻底解决了这个问题,因为它会先对查询字符串和值都执行分析器(www.elastic.co/docs/refere...)处理,将文本分词并转换为小写词项,然后再按词项进行匹配:

sql 复制代码
`

1.  FROM app_logs
2.  | WHERE MATCH(TO_TEXT(message), "fox")

`AI写代码

这个查询会匹配如下内容:

  • "The quick brown fox"

  • "FOX spotted near the henhouse"

但不会匹配:

  • "Outfoxed by the competition"

  • "FOXTROT protocol enabled"

无论单词周围有什么标点符号,都不会影响匹配结果。

当然,这同样适用于多词查询,例如:

less 复制代码
`MATCH(TO_TEXT(message), "brown fox")` AI写代码

它的行为也完全符合你的直觉。

目前,Elastic 正在开发对 36 种专用语言分析器(language analyzers)的支持,使从未建立索引或映射的数据也能够针对自然语言执行全文搜索。

未建立索引和未映射数据的全文搜索应用场景

前面的示例搜索的是由已映射字段计算得到的值。而 ES|QL MATCH 应用于表达式的真正价值,在于它能够搜索那些过去根本无法搜索的数据。下面来看几个典型场景。

如何在不添加映射的情况下搜索 ES|QL 中的未映射字段

有时,你会有意不为某些字段创建映射,例如冗长的堆栈跟踪(stack trace)、原始请求负载(raw request payload),甚至调试信息(debug blob)。

为这些字段建立索引意味着每个文档都会额外占用磁盘和堆内存,而对于可能一个季度才查询一次的字段来说,这种成本并不值得。

过去,这个决定几乎是不可逆的,因为未映射字段完全无法参与查询。

在 Elasticsearch 9.5 中,你可以使用 SET unmapped_fields="load",让 ES|QL 直接从源文档(_source)加载未映射字段,并将其视为 keyword 类型。随后,再使用 TO_TEXT 包装该字段,就可以对它执行全文搜索:

sql 复制代码
`

1.  SET unmapped_fields="load";
2.  FROM app_logs
3.  | WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
4.  | KEEP @timestamp, service.name, message

`AI写代码

这里,stack_trace 从未建立映射。每个值都会直接从原始文档中读取,并在查询时动态进行文本分析,然后逐行匹配。

这确实需要更多计算,因此速度永远无法与基于倒排索引(inverted index)的查询相比。

但现在,那个你没有建立索引的字段,不再意味着"无法搜索"。

你既可以在日常情况下保持映射精简,又能在真正需要时回答那些一个季度才会提出一次的问题。

无需重新建立索引,对 keyword 字段执行全文搜索

keyword 字段用途广泛。

它支持精确匹配、快速聚合以及排序,因此许多字段都会映射为 keyword 类型。

但映射是在数据写入时决定的,而需求却可能发生变化。

例如,product_name 最初被映射为 keyword,因为仪表板需要按产品名称进行聚合。但在积累了一年的产品数据之后,有人希望能够按产品名称进行全文搜索。

过去的解决方案是:

  • 将字段映射改为 text(或增加一个 multi-field);

  • 对所有数据重新建立索引(reindex)。

这通常既耗时又昂贵,因此很多用户根本不愿意这么做。

现在,只需要调用一个函数:

sql 复制代码
`

1.  FROM products
2.  | WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
3.  | KEEP product_name, brand, price

`AI写代码

TO_TEXT 会在查询时将 keyword 值动态转换为 text,因此 MATCH 会对它执行文本分析,而不是进行精确匹配。

这样,你无需修改映射,也无需重新建立索引,就可以对 keyword 字段执行全文搜索。

当然,如果全文搜索最终成为日常查询,把字段真正映射为 text 仍然是更好的长期方案。但 TO_TEXT 可以让你今天就得到答案,而无需任何额外的数据处理。

跨不同映射的索引搜索同一个字段

ES|QL 可以同时查询多个索引,而同一个字段在不同索引中并不要求具有相同的数据类型。

当某个字段在不同索引中具有不同类型时,ES|QL 会将其视为联合类型(union type),并通过类型转换函数来解决差异。

例如,message 字段在今年的索引模板中是 text 类型,而去年则是 keyword 类型:

scss 复制代码
`

1.  FROM logs-2025, logs-2026
2.  | EVAL msg = TO_TEXT(message)
3.  | WHERE MATCH(msg, "connection reset")

`AI写代码

无论数据来自 text 索引还是 keyword 索引,所有值都会在查询时进行文本分析。

旧索引中的 keyword 值也会像其他文本一样完成分词和小写化处理,因此 "connection reset" 同样能够匹配 "Connection RESET by peer",无论它位于哪个索引。

另一种有趣的情况是:某个字段只在一个索引中建立了映射,而在另一个索引中虽然存在,但没有建立映射。

例如:

sql 复制代码
`

1.  SET unmapped_fields="load";
2.  FROM logs-2025, logs-2026
3.  | WHERE MATCH(TO_TEXT(error_details), "timeout")

`AI写代码

这里有一个值得注意的细节。

如果 error_detailslogs-2026 中已建立映射,而在 logs-2025 中没有映射,Elasticsearch 无法将该查询下推(push down)到 Lucene,因为对于未映射该字段的索引,Lucene 会直接返回零匹配结果,从而导致错误的查询结果。

因此,查询规划器(planner)会识别出该字段可能未建立映射,并改为对所有来源的文档逐行执行整个 MATCH 查询。

你无需知道哪些索引为该字段建立了映射,哪些没有。

查询会自动处理这些差异,并直接返回正确的结果。

ES|QL 如何在没有倒排索引的情况下于查询时分析文本

当 ES|QL 规划针对表达式执行 MATCH 查询时,它会首先将查询字符串分析(analyze)一次,将其转换为一组词项(terms)。

随后,每一行数据如何进行匹配,取决于表达式的数据类型:

表达式类型 处理方式 匹配行为
text(通过 TO_TEXT 转换) 使用分析器将值分词并转换为小写词项 按词项进行匹配;只要任意词项与任意查询词项相等,该行即匹配(OR 语义)
keywordipdate、数值类型 不进行文本分析;查询常量仅转换一次为对应的原生类型 对每一行执行精确匹配

这两种处理方式都会绕过 Lucene,而是直接逐行计算数据。

对于非 text 类型,这种处理方式与针对这些字段类型下推到 Lucene 执行 match 查询时的行为完全一致,因此无论查询是否命中索引,其语义都保持一致。

倒排索引(inverted index)会在数据写入(ingest)阶段完成分析工作,因此查询时无需访问不匹配的文档。

而运行时(runtime)的 MATCH 则是在查询阶段对每一行数据执行分析。

前者之所以速度快,是因为大部分工作已经在数据写入时完成;后者之所以更加灵活,是因为数据根本无需建立索引即可执行全文搜索。

ES|QL 全文搜索的下一步计划

本文介绍的功能只是一个更大计划的第一步,其目标是让 ES|QL 能够对任何数据执行搜索,而不仅仅是那些提前建立了索引的数据。

前文提到的限制正在积极改进中,未来的路线图还包括:

  • 相关性评分(Scoring)

    运行时匹配(runtime matches)将参与 _score 的计算,因此即使数据从未建立索引,你也可以按相关性排序搜索结果。

  • 表达式上的 MATCH_PHRASE

    该功能现已在 Elastic Cloud Serverless 中提供,并将于 Elastic Stack 9.6 中推出。

  • 可配置分析器(Configurable analyzers)

    为表达式上的 MATCHMATCH_PHRASE 提供分析器支持,从而在查询时使用语言分析器、词干提取(stemming)和同义词(synonyms)。

  • 匹配选项(Match options)

    为运行时匹配提供 fuzzinessoperator 等查询选项。

  • 向量搜索(Vector search)

    在查询过程中为每一行数据生成向量嵌入(embeddings),并对运行时 dense_vector 表达式执行 k 最近邻(kNN)搜索,将语义搜索能力扩展到未建立索引的数据。

立即体验 ES|QL 表达式全文搜索

你现在就可以体验运行时搜索。

该功能现已在 Elastic Cloud Serverless 中提供------新的 ES|QL 功能通常都会首先在这里发布;同时,它也作为 Elasticsearch 9.5 的技术预览(technical preview)功能提供。

建议先阅读搜索函数(search functions)参考文档,并查看 ES|QL 限制(limitations)页面,了解当前功能边界。

之所以以技术预览形式发布,是因为我们希望获得你的反馈。如果你对从未建立索引的数据执行搜索,并且结果让你感到惊喜 ------ 无论是正面的还是负面的 ------ 我们都非常希望听到你的反馈

原文:www.elastic.co/search-labs...

相关推荐
Elasticsearch7 小时前
用 start-local 脚本在本地运行 Elastic Stack 并创建 AI agents
elasticsearch
Elasticsearch1 天前
一次编辑,更新所有仪表板:使用 Terraform 大规模管理 Kibana 可观测性配置
elasticsearch
Elasticsearch1 天前
足够接近就是足够快:ES|QL Fast 模式如何让 Kibana 仪表板速度提升最高 100 倍
elasticsearch
Elasticsearch1 天前
一分钟内从提示词生成仪表板,成本降低 5 倍:Kibana 中的 AI 仪表板和自定义 Vega-Lite 图表
elasticsearch
雾屿_Mistisle1 天前
敏感文件泄露
大数据·elasticsearch·搜索引擎
Elasticsearch1 天前
Elastic 9.5: Columnar 、 VectorDB 索引模式与自动校准,以及由 AI 驱动的告警分类整理
elasticsearch
晚安code1 天前
一、Elasticsearch查询的 DSL 骨架:先把 JSON 看懂
elasticsearch
XS0301061 天前
Git远程仓库实操笔记
笔记·git·elasticsearch
love530love1 天前
Ubuntu系统通过Homebrew安装Lightpanda完整实战教程(含端口占用排坑)
大数据·linux·运维·人工智能·elasticsearch·搜索引擎