在 Elasticsearch 中高速实现类 LIKE 搜索的部分匹配搜索方法

在工作中,经常听到有人反馈关系型数据库(MySQL、PostgreSQL 等)搜索功能存在 “LIKE 搜索速度慢” 的问题。尤其是在处理大量数据的系统中,LIKE 搜索往往会导致性能下降,搜索响应延迟的问题屡见不鲜。因此,越来越多的案例开始考虑从关系型数据库迁移到 Elasticsearch 来解决这一问题。

Elasticsearch 是一款能够实现高速、灵活全文搜索的强大搜索引擎。但要充分发挥其性能,恰当的数据设计与查询设计至关重要。

本文将聚焦于 “如何在 Elasticsearch 中高速实现类似 SQL LIKE 搜索的部分匹配搜索” 进行讲解。

1. 数据类型的差异

在 Elasticsearch 中进行字符串搜索前,首先需要理解 keyword 型与 text 型的区别。向 Elasticsearch 中注册字符串型字段时,需预先设定该字段采用哪种数据类型。

keyword 型:适用于精确字符串匹配

keyword 型是将字符串以原始形式注册到索引中的数据类型。由于其能高速执行完全匹配搜索、排序、聚合等操作,因此像 ID、商品分类这类短字符串通常会注册为 keyword 型。

text 型:适用于全文搜索

text 型擅长自然语言处理与全文搜索。设置为该类型的字段在注册到索引时,会通过分析器(Analyzer)将文本按单词或短语拆分后再进行注册。

文本按何种规则拆分为令牌(Token)由分析器决定,需根据搜索需求设计合适的分析器。

参考文档:analyzer | Elasticsearch Guide [8.16] | Elastic

我们可使用分析 API(Analyze API)确认字符串是如何被拆分令牌并注册的。以下是使用默认分析器(standard Analyzer)进行令牌化的示例。

POST _analyze
{
  "text":     "Elasticsearch is powerful"
}

从结果可以看出,文本被拆分为 “elasticsearch”“is”“powerful” 三个令牌。

{
  "tokens": [
    {
      "token": "elasticsearch",
      "start_offset": 0,
      "end_offset": 13,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "is",
      "start_offset": 14,
      "end_offset": 16,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "powerful",
      "start_offset": 17,
      "end_offset": 25,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

通常情况下,部分匹配搜索的目标字段会注册为 text 型。

2. 实现部分匹配搜索的方法及特点

keyword 型字段的部分匹配搜索

对于 keyword 型字段,也可使用 wildcard 查询实现部分匹配搜索。wildcard 查询与 LIKE 搜索类似,是按特定模式匹配字符串的查询,其使用方式与 SQL 的 LIKE 搜索直观上较为接近。

但需注意,wildcard 查询的计算成本极高,索引规模越大,对搜索系统产生不良影响的可能性就越高。

wildcard 查询示例(搜索以 “Elasticsearch” 开头的字符串)

GET test_index/_search
{
  "query": {
    "wildcard": {
      "message": {
        "value": "Elasticsearch*"
      }
    }
  }
}

text 型字段的部分匹配搜索

对 text 型字段进行部分匹配搜索时,通常使用 match 查询或 match_phrase 查询。与 text 型字段拆分令牌后注册到索引的逻辑相同,搜索字符串也会被拆分令牌,只要目标字段与搜索字符串的令牌能够匹配,对应的结果就会命中。其中,match 查询适用于单词搜索,match_phrase 查询适用于短语搜索。

match 查询示例(搜索以 “Elasticsearch” 开头的字符串)

GET test_index/_search
{
  "query": {
    "match": {
      "message": "Elasticsearch"
    }
  }
}

3. 恰当的分析器设计

在 Elasticsearch 中进行字符串搜索时,虽然默认使用 match 或 match_phrase 查询,但如果分析器设置不当,可能会出现无法返回预期结果、反而返回大量无关结果等问题。

例如,在第 1 部分的示例中,若向已注册的 test_index 搜索 “power” 字符串,将无法命中结果。

搜索 “Elastic” 的查询

GET test_index/_search
{
  "query": {
    "match": {
      "message": "power"
    }
  }
}

搜索结果

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  }
}

无法命中的原因是,索引中注册的令牌为 “elasticsearch”“is”“powerful”,而 “power” 这一令牌并未被注册。因此,要实现预期的搜索结果,必须分别合理设计:

  • 数据注册时使用的分析器
  • 搜索字符串使用的分析器

4. 对 text 型字段实现类 LIKE 从句的搜索方法

要对 text 型字段实现类似 LIKE 从句的 “搜索包含特定模式字符串” 的功能,可通过对应用了包含 N-gram 令牌生成器(tokenizer)的分析器的字段执行 match_phrase 查询来实现。

使用 N-gram 令牌生成器时,注册的字符串会被机械地按任意长度拆分令牌。例如,将 “Elasticsearch” 这一字符串按 2 个字符(bi-gram)拆分 N-gram 令牌,会得到 “El”“la”“as”……“rc”“ch” 等令牌,这些令牌会被注册到索引中。

可在创建索引时按如下方式设置分析器,为特定字段配置包含 N-gram 令牌生成器的分析器。

PUT test_index
{
  "mappings": {
    "properties": {
      "message": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 2
        }
      }
    }
  }
}

即便使用 match_phrase 搜索 “lastic”,搜索字符串也会被拆分为 “la”“as”“st”“ti”“ic” 等令牌,只有包含所有这些令牌的字符串才会命中。通过这种方式,即可实现相当于 LIKE 搜索的部分匹配搜索。

GET test_index/_search
{
  "query": {
    "match_phrase": {
      "message": "lastic"
    }
  }
}
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.1507283,
    "hits": [
      {
        "_index": "test_index",
        "_id": "ESDup5MBGJks9_KvUZZQ",
        "_score": 1.1507283,
        "_source": {
          "message": "Elasticsearch is powerful"
        }
      }
    ]
  }
}

5. 总结

本次介绍了在 Elasticsearch 中实现类 LIKE 搜索的部分匹配方法。

若仅需实现部分匹配搜索,使用 wildcard 查询即可达成,但从系统运行时的搜索性能等角度考量,尽管需要提前进行分析器(Analyzer)设置等额外工作,采用 N-gram 结合 match_phrase 查询仍是基础且推荐的方案

当然,根据具体的搜索需求,还需要进行更细致的分析器及查询设计。尤其在搜索性能与搜索精度之间如何取得平衡,是运用 Elasticsearch 过程中无法回避的关键问题。希望本文能为 Elasticsearch 的搜索设计提供参考。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注