Elasticsearch 如何实现类似 MySQL 的模糊查询优化?

文章导读
先说结论:Elasticsearch 利用倒排索引和分词策略能高效实现模糊匹配,而 MySQL 的 LIKE '%...%' 无法利用索引,数据量大时性能急剧下降。
📋 目录
  1. 核心原理与选型
  2. 实操:创建支持模糊查询的索引
  3. 数据同步方案
  4. 查询与验证
  5. 风险与注意事项
  6. 参考资料
A A

先说结论:Elasticsearch 利用倒排索引和分词策略能高效实现模糊匹配,而 MySQL 的 LIKE '%...%' 无法利用索引,数据量大时性能急剧下降。

  • 先定位:确认业务是需要前缀匹配、后缀匹配还是任意子串匹配,不同场景对应 ES 的不同查询方式。
  • 先做:优先使用 ngram 分词器构建子串索引,避免在高 QPS 接口直接暴露 wildcard 查询。
  • 再验证:对比迁移前后的查询耗时与 CPU 占用,确保索引膨胀率在可接受范围内。

核心原理与选型

MySQL 的 LIKE '%keyword%' 查询无法利用 B+ 树索引,因为前缀通配符导致索引失效,数据库被迫进行全表扫描。随着数据量增长,这种查询会导致 CPU 飙升和响应延迟。

Elasticsearch 基于倒排索引,将文本分词后建立词项到文档的映射。通过 ngram 分词器,可以将文本切分为连续字符序列,使得任意子串都能命中索引。但需注意,ngram 会显著增加索引体积和写入耗时,适用于读多写少的场景。

实操:创建支持模糊查询的索引

以下是使用 curl 命令创建索引的完整示例,包含 ngram 分词器配置。注意 min_grammax_gram 的设置直接影响索引大小。

curl -X PUT "localhost:9200/product_index" -H 'Content-Type: application/json' -d '
{
  "settings": {
    "analysis": {
      "analyzer": {
        "ngram_analyzer": {
          "type": "custom",
          "tokenizer": "ngram_tokenizer",
          "filter": ["lowercase"]
        }
      },
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 10
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "ngram_analyzer",
        "search_analyzer": "standard"
      }
    }
  }
}'

配置说明:

  • search_analyzer 设置为 standard,确保查询时不分词过度,只匹配索引中已生成的 ngram 项。
  • max_gram 不宜过大,建议根据业务关键词长度设定,过大会导致索引体积膨胀数倍。

数据同步方案

建立 MySQL 到 Elasticsearch 的同步机制是落地的关键。以下是 Logstash 配置文件片段示例,用于监听 MySQL binlog 或定时同步。

input {
  jdbc {
    jdbc_connection_string => "jdbc:mysql://localhost:3306/shop"
    jdbc_user => "root"
    schedule => "*/5 * * * *"
    statement => "SELECT id, content FROM products WHERE update_time > :sql_last_value"
  }
}

filter {
  # 可根据需要添加数据清洗逻辑
}

output {
  elasticsearch {
    hosts => ["localhost:9200"]
    index => "product_index"
    document_id => "%{id}"
  }
}

若对实时性要求极高,建议使用 Canal 监听 binlog 直接推送至 ES,减少同步延迟。

Elasticsearch 如何实现类似 MySQL 的模糊查询优化?

查询与验证

索引创建并同步数据后,使用 match 查询即可命中子串:

curl -X GET "localhost:9200/product_index/_search" -H 'Content-Type: application/json' -d '
{
  "query": {
    "match": {
      "content": "关键词"
    }
  }
}'

验证方法:

  1. 耗时对比:记录迁移前后接口的平均响应时间。可通过 Kibana 慢查询日志(Slow Log)监控 ES 查询耗时。
  2. 资源监控:观察 ES 集群的 CPU 使用率。模糊查询若未优化,单个查询可能触发数万次词项比较,导致 CPU 占用率飙升。
  3. 结果准确性:抽样检查查询结果,确保 ngram 分词没有导致过多的无关匹配。可通过调整 min_gram 大小来控制匹配精度。

风险与注意事项

1. 索引体积膨胀:ngram 分词会生成大量子串 token,导致索引文件大小显著增加(可能达到原文本的 3-5 倍)。需评估存储成本,合理设置 max_gram

2. 写入性能下降:由于需要生成更多词项,索引构建和数据写入速度会变慢。不建议在写入频繁的热点表上直接使用。

3. Wildcard 性能陷阱:wildcard 查询底层需遍历全部词项,不利用 Term Index 加速。仅适合日均查询量低、索引总词项数有限的轻量级场景,禁止在高 QPS 接口直接暴露。

4. 中文分词干扰:标准分词器对中文处理效果有限。建议配合 ik 分词器或专门针对中文优化 ngram 配置,提升中文模糊查询召回率。

参考资料

  • Elasticsearch 官方文档:Analysis API
  • Elasticsearch 官方文档:Mapping
  • 阿里云开发者社区:Elasticsearch 实现 Mysql 的 Like 效果