ElIauk

Elasticsearch 入门与倒排索引

从“搜商品名”说起 MySQL 里搜商品名,常见写法是 LIKE '%手机%'。B+ 树索引只能做前缀匹配,前后都带通配符就只能全表扫描,几百万行数据下接口直接超时。Elasticsearch 的思路完全不同:它把文本拆成词,建立“词 → 文档 ID”的映射,查询时先找到词,再取出对应的文档集合,不

评论
Elasticsearch 入门与倒排索引

从“搜商品名”说起

MySQL 里搜商品名,常见写法是 LIKE '%手机%'。B+ 树索引只能做前缀匹配,前后都带通配符就只能全表扫描,几百万行数据下接口直接超时。Elasticsearch 的思路完全不同:它把文本拆成词,建立“词 → 文档 ID”的映射,查询时先找到词,再取出对应的文档集合,不需要逐行扫描。

这就是倒排索引。正排索引是“文档 → 包含哪些词”,倒排索引反过来,是“词 → 出现在哪些文档”。搜索、聚合、排序都建立在这个结构上,理解它,才能理解为什么 mapping 和分词器这么重要。

倒排索引怎么工作

写入一条文档时,Elasticsearch 先做字符过滤、分词、词项过滤,得到一组 term,然后更新 term dictionary 和 posting list。term dictionary 是有序的,查询时可以用二分或 FST 快速定位;posting list 记录包含该 term 的文档 ID,以及词频、位置等信息,用于算相关性和短语匹配。

分词方式直接决定召回。英文按空格和标点切分比较自然,中文用默认的 standard 分词器会按单字切,“手机壳”变成“手”“机”“壳”,搜“手机”能匹配,但语义已经散了。生产环境一般安装 IK 插件,写入用 ik_max_word 细粒度切,查询用 ik_smart 粗粒度切,兼顾召回和性能。

curl -X POST "http://localhost:9200/_analyze" \
  -H 'Content-Type: application/json' \
  -d '{"analyzer":"ik_max_word","text":"Spring Boot 3.2 实战"}'

返回的 tokens 就是实际写入倒排索引的词。分词结果和预期不一致时,先调这个接口,不要直接去查数据。

建索引:mapping 要先想清楚

Elasticsearch 8.x 默认开启安全认证,本地用 curl 记得带上用户名密码或证书。下面是一个商品索引的简化 mapping:

PUT products
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "brand": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 },
      "created_at": { "type": "date" }
    }
  }
}

text 会分词,适合全文检索;keyword 不分词,适合精确匹配、聚合和排序。商品名用 text,品牌用 keyword,价格用 scaled_float 避免浮点误差。字段类型一旦写入数据就不能直接修改,要改只能新建索引、reindex、再切别名,所以 mapping 要在建索引时定好。

写入和查询:

POST products/_doc/1
{
  "name": "无线降噪耳机",
  "brand": "Example",
  "price": 899.00,
  "created_at": "2024-07-01T10:00:00Z"
}

GET products/_search
{
  "query": {
    "match": { "name": "降噪耳机" }
  }
}

match 会对查询词分词后再匹配,term 则拿原始值精确匹配。对 text 字段用 term 查“降噪耳机”,除非写入时分词器恰好把它切成一个词,否则查不到;对 keyword 字段用 match 做全文检索,也只会整串匹配。这两个混用是最常见的“搜不到数据”原因。

查询与相关性

实际搜索很少只用单个 match。常见结构是 bool 查询:must 参与算分,filter 只过滤不算分并且可以缓存,should 提高匹配度,minimum_should_match 控制至少命中几个词。商品搜索里,分类、价格区间、上下架状态放 filter,关键词放 must,品牌可以放 should 做加权。

分页要注意 from + size 默认上限是 10000。超过这个值会直接报错,不是性能慢的问题。深分页要用 search_after 配合 PIT(point in time),保证翻页期间索引不变;需要跳页的场景,应该重新设计交互,而不是硬翻。

常见坑

1. 用 term 查 text 字段

写入时“无线降噪耳机”被 IK 切成多个词,term 查整串自然没有结果。全文检索用 match,精确匹配用 keyword 字段加 term。

2. 动态 mapping 把类型推断错

第一条文档里 price 是整数,第二条写入 899.50 就可能报错;日期字符串格式不统一也会导致解析失败。生产索引应该显式定义 mapping,关闭不必要的动态字段,避免字段爆炸。

3. 中文分词器没装或没生效

analyzer 配置了 ik_max_word,但插件没安装,建索引时直接报错。升级 ES 版本时,IK 插件版本必须和 ES 主版本一致,否则节点起不来。

4. 写完立刻查不到

Elasticsearch 默认每秒 refresh 一次,写入成功后要等 refresh 才能被搜索到。测试里可以用 refresh=wait_for,业务上要接受这个延迟,不要把它当成数据丢失。

5. 分片和副本拍脑袋定

分片数在索引创建后不能改,副本可以改。单分片数据量建议控制在几十 GB 以内,分片过多会增加集群元数据和查询开销。小集群先按 1 到 3 个主分片起步,结合数据增长再调整。

排查步骤

先看集群和索引状态:

curl -u elastic:password "http://localhost:9200/_cluster/health?pretty"
curl -u elastic:password "http://localhost:9200/_cat/indices?v"
curl -u elastic:password "http://localhost:9200/products/_mapping?pretty"

搜不到数据时,按“分词 → mapping → 查询语句”的顺序排查:先用 _analyze 确认写入和查询的分词结果,再看字段是 text 还是 keyword,最后确认查询用的是 match 还是 term。慢查询可以用 profile: true 看每个阶段的耗时,但不要在生产对高并发查询长期开启。

小结

Elasticsearch 的核心是倒排索引,倒排索引的质量由分词和 mapping 决定。把字段类型、分词器、查询方式三件事对齐,大部分“搜不到、搜不准”的问题都能避免。它适合搜索和分析,不适合当主数据库,写入和事务能力都要弱于 MySQL。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者