
作者来自 Elastic Kevin Corcoran 及 Ioana TagirtaMATCH和TO_TEXT将全文搜索能力带到你从未建立索引的数据中。在 ES|QL 中你可以对计算列、未映射字段以及联邦数据源执行全文搜索。ES|QLMATCH现在可以对你从未建立索引的数据执行全文搜索。无论是计算列、未映射字段、动态拼接生成的字符串还是存储在 S3 中的联邦数据都可以进行全文搜索。新的TO_TEXT函数会告诉 ES|QL 将任意字符串视为可分析analyzable的文本因此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在这个示例中summary没有映射mapping也没有分析器analyzer配置。它也没有关联任何倒排索引inverted index。它仅在当前查询执行期间存在但现在你依然可以对它执行全文搜索。这一能力由两项新增功能共同实现。首先MATCH的第一个参数现在可以接受任意表达式而不仅仅是已映射字段。这包括由EVAL生成的列内联使用的函数返回值直接从原始文档读取的未映射字段。此外所有MATCH通常支持的数据类型在这种新用法中同样受支持。第二项新增功能是TO_TEXT函数这是 ES|QL 第一个输出text类型的转换函数。此前text类型的列只能来自已建立索引的映射字段而所有由 ES|QL 表达式生成的字符串都会被视为keyword类型而不是text类型。这种区别非常重要因为MATCH对两者的处理方式不同text类型会进行文本分析analysiskeyword类型则进行精确匹配这与针对已建立索引的keyword字段执行MATCH查询时最终重写为term查询的行为一致。TO_TEXT(x)就是在告诉 ES|QL“请把这个字符串当作全文文本来处理。”该功能作为 Elasticsearch 9.5 的技术预览发布因此目前仍存在一些限制目前仅支持过滤filtering。针对表达式执行的MATCH查询暂时不会参与相关性评分relevance score只有针对已建立索引字段的匹配才会影响评分。在表达式上执行MATCH时目前还不支持fuzziness等查询选项。运行时文本runtime text统一使用标准分析器standard analyzer进行分析目前尚不能自定义配置。Elastic 正在持续完善这些限制并将在后续版本中逐步支持相关能力。为什么在 ES|QL 中使用全文搜索而不是LIKE或RLIKEES|QL 已经提供了两种无需索引即可搜索字符串的方式LIKE通配符模式匹配和RLIKE正则表达式。它们都可以作用于任意字符串表达式因此很自然会问MATCH又带来了什么答案是文本分析analysis—— 一种更高级的搜索方式它利用词干提取stemming、同义词synonyms以及停用词stopwords处理等技术来执行全文搜索。LIKE本质上只是简单的子字符串匹配并不了解字符串中的单词。例如你想查找与狐狸fox相关的日志FROM app_logs | WHERE message LIKE *fox*这个查询会遗漏Fox spotted near the henhouse因为大小写不同却会匹配Outfoxed by the competition尽管它并不是在讨论狐狸。也就是说它在两个方向上都会出现问题因大小写导致漏匹配false negatives因子字符串出现在其他单词中导致误匹配false positives。正则表达式可以解决大小写问题但处理单词边界会迅速变得复杂。例如FROM app_logs | WHERE message RLIKE (.* )?[Ff][Oo][Xx]([ ,.:;].*)?即便如此这个表达式仍然不够完善。它无法匹配句末跟着!或?的fox也没有考虑制表符、引号或括号等情况。每修复一种情况正则表达式都会变得更长而下一位阅读该查询的人还得花时间弄清楚它到底在做什么。MATCH则彻底解决了这个问题因为它会先对查询字符串和值都执行分析器https://www.elastic.co/docs/reference/text-analysis/analyzer-reference处理将文本分词并转换为小写词项然后再按词项进行匹配FROM app_logs | WHERE MATCH(TO_TEXT(message), fox)这个查询会匹配如下内容The quick brown foxFOX spotted near the henhouse但不会匹配Outfoxed by the competitionFOXTROT protocol enabled无论单词周围有什么标点符号都不会影响匹配结果。当然这同样适用于多词查询例如MATCH(TO_TEXT(message), brown fox)它的行为也完全符合你的直觉。目前Elastic 正在开发对 36 种专用语言分析器language analyzers的支持使从未建立索引或映射的数据也能够针对自然语言执行全文搜索。未建立索引和未映射数据的全文搜索应用场景前面的示例搜索的是由已映射字段计算得到的值。而 ES|QLMATCH应用于表达式的真正价值在于它能够搜索那些过去根本无法搜索的数据。下面来看几个典型场景。如何在不添加映射的情况下搜索 ES|QL 中的未映射字段有时你会有意不为某些字段创建映射例如冗长的堆栈跟踪stack trace、原始请求负载raw request payload甚至调试信息debug blob。为这些字段建立索引意味着每个文档都会额外占用磁盘和堆内存而对于可能一个季度才查询一次的字段来说这种成本并不值得。过去这个决定几乎是不可逆的因为未映射字段完全无法参与查询。在 Elasticsearch 9.5 中你可以使用SET unmapped_fieldsload让 ES|QL 直接从源文档_source加载未映射字段并将其视为keyword类型。随后再使用TO_TEXT包装该字段就可以对它执行全文搜索SET unmapped_fieldsload; FROM app_logs | WHERE MATCH(TO_TEXT(stack_trace), java.lang.NullPointerException) | KEEP timestamp, service.name, message这里stack_trace从未建立映射。每个值都会直接从原始文档中读取并在查询时动态进行文本分析然后逐行匹配。这确实需要更多计算因此速度永远无法与基于倒排索引inverted index的查询相比。但现在那个你没有建立索引的字段不再意味着“无法搜索”。你既可以在日常情况下保持映射精简又能在真正需要时回答那些一个季度才会提出一次的问题。无需重新建立索引对keyword字段执行全文搜索keyword字段用途广泛。它支持精确匹配、快速聚合以及排序因此许多字段都会映射为keyword类型。但映射是在数据写入时决定的而需求却可能发生变化。例如product_name最初被映射为keyword因为仪表板需要按产品名称进行聚合。但在积累了一年的产品数据之后有人希望能够按产品名称进行全文搜索。过去的解决方案是将字段映射改为text或增加一个 multi-field对所有数据重新建立索引reindex。这通常既耗时又昂贵因此很多用户根本不愿意这么做。现在只需要调用一个函数FROM products | WHERE MATCH(TO_TEXT(product_name), wireless noise cancelling headphones) | KEEP product_name, brand, priceTO_TEXT会在查询时将keyword值动态转换为text因此MATCH会对它执行文本分析而不是进行精确匹配。这样你无需修改映射也无需重新建立索引就可以对keyword字段执行全文搜索。当然如果全文搜索最终成为日常查询把字段真正映射为text仍然是更好的长期方案。但TO_TEXT可以让你今天就得到答案而无需任何额外的数据处理。跨不同映射的索引搜索同一个字段ES|QL 可以同时查询多个索引而同一个字段在不同索引中并不要求具有相同的数据类型。当某个字段在不同索引中具有不同类型时ES|QL 会将其视为联合类型union type并通过类型转换函数来解决差异。例如message字段在今年的索引模板中是text类型而去年则是keyword类型FROM logs-2025, logs-2026 | EVAL msg TO_TEXT(message) | WHERE MATCH(msg, connection reset)无论数据来自text索引还是keyword索引所有值都会在查询时进行文本分析。旧索引中的keyword值也会像其他文本一样完成分词和小写化处理因此connection reset同样能够匹配Connection RESET by peer无论它位于哪个索引。另一种有趣的情况是某个字段只在一个索引中建立了映射而在另一个索引中虽然存在但没有建立映射。例如SET unmapped_fieldsload; FROM logs-2025, logs-2026 | WHERE MATCH(TO_TEXT(error_details), timeout)这里有一个值得注意的细节。如果error_details在logs-2026中已建立映射而在logs-2025中没有映射Elasticsearch 无法将该查询下推push down到 Lucene因为对于未映射该字段的索引Lucene 会直接返回零匹配结果从而导致错误的查询结果。因此查询规划器planner会识别出该字段可能未建立映射并改为对所有来源的文档逐行执行整个MATCH查询。你无需知道哪些索引为该字段建立了映射哪些没有。查询会自动处理这些差异并直接返回正确的结果。ES|QL 如何在没有倒排索引的情况下于查询时分析文本当 ES|QL 规划针对表达式执行MATCH查询时它会首先将查询字符串分析analyze一次将其转换为一组词项terms。随后每一行数据如何进行匹配取决于表达式的数据类型表达式类型处理方式匹配行为text通过TO_TEXT转换使用分析器将值分词并转换为小写词项按词项进行匹配只要任意词项与任意查询词项相等该行即匹配OR 语义keyword、ip、date、数值类型不进行文本分析查询常量仅转换一次为对应的原生类型对每一行执行精确匹配这两种处理方式都会绕过 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为表达式上的MATCH和MATCH_PHRASE提供分析器支持从而在查询时使用语言分析器、词干提取stemming和同义词synonyms。匹配选项Match options为运行时匹配提供fuzziness、operator等查询选项。向量搜索Vector search在查询过程中为每一行数据生成向量嵌入embeddings并对运行时dense_vector表达式执行 k 最近邻kNN搜索将语义搜索能力扩展到未建立索引的数据。立即体验 ES|QL 表达式全文搜索你现在就可以体验运行时搜索。该功能现已在 Elastic Cloud Serverless 中提供——新的 ES|QL 功能通常都会首先在这里发布同时它也作为 Elasticsearch 9.5 的技术预览technical preview功能提供。建议先阅读搜索函数search functions参考文档并查看 ES|QL 限制limitations页面了解当前功能边界。之所以以技术预览形式发布是因为我们希望获得你的反馈。如果你对从未建立索引的数据执行搜索并且结果让你感到惊喜 —— 无论是正面的还是负面的 —— 我们都非常希望听到你的反馈。原文https://www.elastic.co/search-labs/blog/full-text-search-unindexed-data