Elasticsearch ES|QL 可将全文本搜索功能应用于从未建立索引的数据
MATCH 和 TO_TEXT 可将全文本搜索功能应用于从未建立索引的数据。在 ES|QL 中搜索计算列、未映射字段和联合源。
ES|QL MATCH 现在可以对从未建立索引的数据进行全文本搜索。计算列、未映射字段、实时汇编的字符串,甚至存储在 S3 中的联合数据。新的 TO_TEXT 函数告诉 ES|QL 将任何字符串视为可分析的文本,因此 MATCH 可以对仅在查询生命周期内存在的值进行分词、大小写折叠和词元匹配。这超越了大多数查询引擎为未建立索引的字符串提供的 LIKE 和 RLIKE 模式匹配:这是真正的分析。目前已在 Elastic Cloud Serverless 中提供,并在 Elasticsearch 9.5 中作为技术预览版提供。
MATCH 和 TO_TEXT 如何启用任何 ES|QL 表达式的全文本搜索
让我们从一个在 Elasticsearch 9.4 中无法实现的查询开始,该查询使用了EVAL 命令:
FROM cooking_blog
| EVAL summary = TO_TEXT(CONCAT(title, description))
| WHERE MATCH(summary, "pancakes")
| KEEP title, author在这个例子中,摘要没有映射或分析器配置。它也没有与任何倒排索引关联。它仅在此查询的生命周期内存在,但现在无论如何您都可以搜索它。两项新增功能使其生效。
首先,MATCH 现在接受任何表达式作为其第一个参数,而不仅仅是映射字段。这包括由 EVAL 生成的列和内联使用的函数结果。它还包括直接从原始文档加载的未映射字段。此外,MATCH 通常接受的所有数据类型在此新用例中均受支持。
其中的第二部分是新的 TO_TEXT 函数,它是第一个生成文本类型输出的 ES|QL 转换函数。此前,文本列只能来自索引映射字段,而 ES|QL 表达式生成的所有字符串都是关键字值,而不是文本。这种区别很重要,因为 MATCH 对两者处理方式不同:文本值被分析,而关键字值被精确比较,这类似于 MATCH 查询在索引关键字字段上的重写为词元查询的方式。TO_TEXT(x) 是告诉 ES|QL:将此字符串视为全文本。
此版本作为 Elasticsearch 9.5 的技术预览版,因此存在一些限制:
当前仅在进行筛选。表达式上的 MATCH 还不会影响相关性得分;只有索引字段匹配才会影响得分。
匹配表达式时,尚不支持模糊性等查询选项。
运行时文本使用标准分析仪进行分析。此功能目前尚不可配置。
目前正在努力解决这些限制。
为什么在 ES|QL 中使用全文本搜索而不是 LIKE 或 RLIKE?
ES|QL 已经提供了两种无需索引即可搜索字符串的方法:LIKE(通配符模式)和 RLIKE(正则表达式)。两者都适用于任何字符串表达式,所以问 MATCH 增加了什么功能是很合理的。答案是分析,这是一种更高级的搜索方式,采用词干提取和同义词等技术。它还使用了停用词处理。
LIKE 是简单的子字符串匹配,无需理解组成字符串的词语。例如,假设您正在寻找有关 fox 的日志消息:
FROM app_logs
| WHERE message LIKE "*fox*"由于大小写问题,这句话漏掉了“Fox spotted near the henhouse”,而与“Outfoxed by the competition”相符,但后者与狐狸根本无关。它在两个方面都存在缺陷,对大小写存在漏报,对隐藏在其他单词中的子字符串存在误报。
正则表达式可以解决大小写问题,但单词边界问题很快就会变得很棘手。类似这样的内容:
FROM app_logs
| WHERE message RLIKE "(.* )?[Ff][Oo][Xx]([ ,.:;].*)?"而且这还不完全正确。它漏掉了句末带 ! 或 ? 的 fox 符号,而且没有提到制表符、引号或括号。每次修复都会使模式变得更长,下一个阅读该查询的人必须反向工程才能弄清楚它实际在做什么。
MATCH 可以解决这个问题,因为它会将查询和值都通过分析器进行处理,分析器会将文本分词为小写词元,然后将词元与词元进行匹配:
FROM app_logs
| WHERE MATCH(TO_TEXT(message), "fox")此查询将匹配“The quick brown fox”和“FOX spotted near the henhouse”之类的值,但不会匹配“Outfoxed by the competition”或“FOXTROT protocol enabled”,无论词语周围是否有任何标点符号。当然,这一切也适用于多词元查询,比如 MATCH(TO_TEXT(message), “brown fox”),正如您所期望的那样。
目前正在努力启用 36 个专用语言分析器,以支持对从未建立索引或映射的数据使用自然语言。
未建立索引和未映射数据的全文本搜索用例
上述示例搜索的是从映射字段计算出的数值。ES|QL MATCH 在表达式上的更有趣的用例涉及以前根本无法搜索的数据。我们来看几个例子。
如何在不添加映射的情况下,在 ES|QL 中搜索未映射字段
有时您会故意在映射中省略某个字段,例如详细的堆栈跟踪或原始请求有效负载。您甚至可以省略故障排查块。对其中一个字段进行索引会占用每个文档的磁盘和堆空间,对于一个可能每季度查询一次的字段来说,这样做并不值得。
这一决定一直是最终的,因为未映射字段对查询完全不可见。在 Elasticsearch 9.5 中,您可以使用 SET unmapped_fields="load" 让 ES|QL 直接从源文档加载未映射的字段作为关键字。接下来,将其包装在 TO_TEXT 函数中,现在就可以对其进行全文本搜索了:
SET unmapped_fields="load";
FROM app_logs
| WHERE MATCH(TO_TEXT(stack_trace), "java.lang.NullPointerException")
| KEEP @timestamp, service.name, message这里,stack_trace 从未被映射。所有数值均取自原始文档并进行实时分析。它们逐行匹配。这才是真正的工作,速度永远比不上倒排索引的查找速度。但现在,您之前未建立索引的字段已经可以搜索了。您可以保持映射在日常用例下的小规模,同时在关键时刻仍能回答季度性问题。
无需重新索引即可对关键字字段进行全文本搜索
关键字字段可以做很多事情。它们提供精确匹配、快速聚合和排序功能,这就是为什么很多字段最终都以这种方式映射的原因。但是映射关系是在数据到达时确定的,很容易出现这样的情况:您想对数据做一些与最初设想不同的事情。也许 product_name 被映射为关键字是因为仪表板会根据它进行聚合,然后,在收到一年的产品数据后,有人希望能够搜索 product_name 的值。
以前的做法是将映射更改为文本(或添加多字段),然后重新索引所有内容。这既费时又费钱,在很多情况下,用户根本不想费这个劲。新答案只需一次函数调用:
FROM products
| WHERE MATCH(TO_TEXT(product_name), "wireless noise cancelling headphones")
| KEEP product_name, brand, priceTO_TEXT 函数会将关键字值即时转换为文本,因此 MATCH 会分析这些文本,而不是对它们进行精确比较。这允许您查询关键字字段,而无需创建映射或重新索引源文档。如果搜索成为日常查询,那么将字段索引为文本仍然是正确的长期举措,但是 TO_TEXT 今天无需任何额外工作即可为您找到答案。
在不同映射的索引上搜索同一字段
ES|QL 可以跨越许多索引,而且同一个字段不必在所有索引中看起来都一样。当同一个字段在不同的索引中具有不同的类型时,ES|QL 将其视为联合类型,转换函数可以解决冲突。我们来看一个例子,今年的索引模板中消息字段的类型是文本,但去年的索引模板中消息字段的类型是关键字:
FROM logs-2025, logs-2026
| EVAL msg = TO_TEXT(message)
| WHERE MATCH(msg, "connection reset")每个值在查询时都会被分析,无论是来自文本索引还是关键字索引。旧索引中的关键字值被分词并小写,因此“connection reset”无论存在哪个索引,都能找到“Connection RESET by peer”。
另一个有趣的例子是,一个字段仅映射在一个索引中,但也存在于另一个索引中(且未映射):
SET unmapped_fields="load";
FROM logs-2025, logs-2026
| WHERE MATCH(TO_TEXT(error_details), "timeout")这里有一个细微差别值得一提。如果 error_details 已映射到 logs-2026 但未映射到 logs-2025,Elasticsearch 无法将此查询下推到 Lucene,因为未映射该字段的索引将默默地返回无匹配项。相反,计划者注意到该字段可能未映射,并逐行评估整个 MATCH,无论这些行来自何处。您无需知道哪些索引映射了该字段;查询只需回答这个问题即可。
ES|QL 如何在不使用倒排索引的情况下在查询时分析文本
当 ES|QL 计划对表达式执行 MATCH 时,它会预先将查询字符串分析成一组词元。每一行的具体求值方式取决于表达式的类型:
表达式类型 | 处理 | 匹配行为 |
文本(通过 TO_TEXT) | 分析器将值分词为小写词元 | 词元对词元比较;如果任何词元等于任何查询词,则该行匹配(OR 语义) |
关键字、IP、日期、数字 | 未进行分析;查询常量已转换为原生类型一次 | 每行精确比较 |
两条路径都完全绕过 Lucene,逐行评估值。非文本路径与针对这些字段类型向下推送到 Lucene 的匹配查询完全一致,因此无论查询是否命中索引,语义都保持一致。
倒排索引查找在数据摄取时进行,在查询时从不触及不匹配的文档。运行时 MATCH 会在查询时,对到达的每一行进行分析。一种方法速度快,因为工作已经完成;另一种方法灵活,因为数据根本不需要建立索引。
ES|QL 全文本搜索的下一步是什么
这篇文章中的所有内容都是更大努力的第一部分,目的是让 ES|QL 中的搜索适用于任何内容,而不仅仅是您提前索引的内容。之前提到的限制正在积极解决,路线图还会进一步改进:
得分。运行时匹配将计入 _score,因此即使数据从未被索引,您也可以按相关性排序。
MATCH_PHRASE 表达式。已在 Elastic Cloud Serverless 中提供,并将于 9.6 中引入 Elastic Stack。
可配置分析器。分析器支持对表达式使用 MATCH 和 MATCH_PHRASE,从而在查询时启用语言分析器、词干提取和同义词。
匹配选项。运行时匹配的选项,如模糊度和操作符。
向量搜索。为每一行生成嵌入,并对运行时 dense_vector 表达式运行 k 近邻 (kNN),从而将语义搜索也带到未建立索引的数据。
立即尝试对表达式进行 ES|QL 全文本搜索
今天就可以尝试运行时搜索。它现已在 Elastic Cloud Serverless 中推出,其中首先推出了新的 ES|QL 功能,并在 Elasticsearch 9.5 中作为技术预览版发布。从搜索函数参考开始,查看 ES|QL 限制页面,以了解当前边界。这是一个技术预览版,因为我们需要您的反馈:如果您搜索的内容从未被编入索引,无论是正面还是负面让您感到惊讶,我们非常希望听到您的反馈。
