Elasticsearch 实战:从倒排索引到搜索调优

举报
yd_262150314 发表于 2026/11/11 01:58:56 2026/11/11
【摘要】 本文从倒排索引讲起,讲清中文分词与Mapping选型、写入链路的refresh机制、查询DSL的分工,以及分片、深分页、通配符等五个性能避坑点,最后给出上线前核对清单,帮你第一次就把ES用对。

电商的商品搜索、日志平台的关键词检索、站内文档搜索,这些场景都有一个共同点:数据量大、查询条件多变、还要支持模糊匹配。直接用数据库的 LIKE 做全表扫描,数据量一上来就扛不住,Elasticsearch 就是为这类场景而生的。本文从倒排索引讲起,讲清楚 Mapping、分词、查询 DSL 与性能调优,帮你第一次就把 ES 用对。

一、倒排索引:ES 搜索快的根本原因

关系型数据库的索引是“正排”的:按主键或字段值找到数据行,适合精确查找。全文搜索恰恰相反:用户给一个词,要快速找出包含这个词的所有文档。倒排索引就是把这个映射关系倒过来:词典里每个词都挂着一个倒排列表(Posting List),记录包含该词的文档 ID。

举个例子,两篇文档写入后,索引内部大致是这样:

  • “手机” → 文档 1、文档 3
  • “充电” → 文档 1、文档 2
  • “快充” → 文档 3

搜索“手机 快充”时,ES 不需要扫描全部文档,只要把“手机”和“快充”两条倒排列表取出来求交集,立刻就能定位到文档 3。倒排列表本身还会按文档 ID 排序并压缩存储,跳表(Skip List)让大列表的定位也很快。理解这一点,就理解了 ES 大多数设计的出发点。

二、分词与 Mapping:写对第一步

中文搜索的第一道坎是分词。ES 自带的 standard 分词器会把中文按单字切开,“无线耳机”会被切成“无 / 线 / 耳 / 机”,搜“无线”能命中,搜“耳机”也能命中,但也可能命中一堆不相关的单字组合。中文场景一般用 IK 分词器,ik_max_word 做最细粒度切分,适合搜索召回;ik_smart 做最粗粒度切分,适合节省索引体积。

Mapping 是索引的“表结构”,字段类型要在建索引时定好,核心区别就两个:

  • text 类型:会被分词,适合全文搜索,比如商品标题、文章正文。
  • keyword 类型:不分词、整体作为一个词,适合精确匹配、排序和聚合,比如订单状态、品牌名。

一个常见的坑是把所有字符串字段都用默认的 text,需要按状态过滤、按品牌聚合时才发现查不动。正确做法是建索引时显式声明:需要搜索的字段用 text,需要过滤和聚合的字段用 keyword,两者都要的用多字段(multi-field)同时保留。

PUT /products
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "fields": { "keyword": { "type": "keyword" } }
      },
      "brand": { "type": "keyword" },
      "price": { "type": "double" }
    }
  }
}

三、写入链路:理解 refresh 与批量

文档写入 ES 不是立刻能搜到的。写入先进入内存缓冲区和事务日志(translog),只有经过一次 refresh(默认每 1 秒)后,文档才会进入新生成的段(Segment)并可被搜索。这就是 ES 的“近实时”:不是严格实时,而是有 1 秒左右的延迟。

写入性能主要看两个开关:批量写入用 Bulk API,一次提交几千条而不是一条一条发,吞吐能差出一个数量级;大批量导入时可以临时把 refresh_interval 调大(比如 30 秒)并关闭副本,导入完再恢复,能显著减少段合并的开销。段合并是后台自动做的,小段合并成大段,查询时需要扫描的段越少,搜索就越快。

四、查询 DSL:match、term 与 bool 的分工

ES 的查询分两类:全文查询会算相关性得分(_score),过滤查询只判断是否满足条件、不算分,过滤结果还会被缓存。写查询时先分清你要的是“ relevance 排序”还是“是 / 否筛选”。

  • match:对查询词先分词再匹配,适合搜索框输入。
  • term:不分词精确匹配,只能用在 keyword、数值、日期字段上。
  • bool:把多个条件组合起来,must 必须满足且算分,filter 必须满足但不算分,should 满足其一即可,must_not 排除。
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "无线耳机" } }
      ],
      "filter": [
        { "term": { "brand": "SoundCore" } },
        { "range": { "price": { "gte": 100, "lte": 500 } } }
      ]
    }
  },
  "aggs": {
    "by_brand": { "terms": { "field": "brand" } }
  },
  "sort": [{ "_score": "desc" }, { "price": "asc" }]
}

这个例子同时演示了聚合:aggs 对品牌做 terms 聚合,一次请求既拿到搜索结果又拿到各品牌的商品数量,列表页的筛选项就是这么来的。

五、性能调优:避开五个常见坑

1. 分片不是越多越好。 每个分片都是一个独立的 Lucene 索引,查询要汇总所有分片的结果。数据量 10GB 用 1 个主分片就够,盲目设 5 个分片只会增加协调开销。一般经验是单个分片控制在 20GB 到 50GB 之间。

2. 深分页不要用 from + size。 翻到第 10000 页时,ES 要在每个分片上取出前 10000 条再汇总排序,越往后越慢。替代方案是 search_after,用上一页最后一条的排序值作为游标往下翻,或者用 Scroll API 做全量导出。

3. 通配符查询不要以前缀开头。 wildcard 查询里 *耳机 这种前导通配符无法走词典,只能逐词扫描,数据量一大就拖垮集群。真有这类需求,考虑用 ngram 分词或改用专门的检索方案。

4. 避免在查询里做大聚合。 对高基数字段(比如用户 ID)做 terms 聚合会产生海量桶,内存和网络开销都大。先确认业务是不是真需要全量聚合,多数场景按 Top N 截断就够了。

5. 监控段数量和堆内存。 段太多会拖慢查询,堆内存超过 32GB 会失去指针压缩的优势。集群规模上来后,索引生命周期管理(ILM)按时间自动滚动新索引、删除旧索引,比人工维护可靠得多。

六、上线前核对清单

  • 分词器是否按语言选对,中文是否用了 IK
  • 字段类型是否区分 text 与 keyword,需要聚合的字段是否可聚合
  • 分片数是否按数据量估算,而不是随手写了个默认值
  • 写入是否用 Bulk 批量,导入期 refresh_interval 是否临时调大
  • 查询是否把筛选条件放进 filter,深分页是否改用 search_after
  • 监控是否覆盖集群健康、堆内存、段数量与慢查询日志

总结

Elasticsearch 的核心就三句话:倒排索引让全文搜索变快,分词与 Mapping 决定搜索准不准,查询 DSL 与分片策略决定跑得快不快。先把索引结构定对,再把查询写对,最后按清单逐项调优,第一次用 ES 就能避开绝大多数生产事故。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。