从数据库到搜索引擎

数据库擅长精确查询与事务,但遇到全文检索就力不从心:LIKE 模糊匹配走不了索引,数据量一大就全表扫描;多字段组合搜索、相关性排序、分词纠错更不是关系型数据库的设计目标。Elasticsearch 就是为"搜索"而生的分布式搜索引擎,基于 Lucene,把文档倒排成索引,毫秒级返回相关性排序结果。

使用 ES 的关键认知是它与数据库的分工:数据库是事实源,ES 是检索副本。数据写入数据库后,通过同步机制把检索字段灌进 ES,查询走 ES,详情回源数据库。这样两边的职责单一,也避免把 ES 当数据库用带来的数据一致性问题。

本文核心知识点
  • 映射定义字段如何被索引与存储。
  • 倒排索引词到文档的映射,搜索的根基。
  • DSLES 的 JSON 查询语法。
  • 分片数据水平切分与副本的单位。
Elasticsearch 索引配图:蓝紫渐变背景左侧多层收窄的纯色梯形漏斗底部发出光束,连接到右侧纯色圆角小方块整齐矩阵,扁平科技插画
漏斗逐层收窄过滤数据,光束落进方块矩阵,正是索引构建与检索的意象。

索引映射与分词

映射决定了字段怎么被处理,写错映射的代价是重建索引。一个字段可以设置 text 用于全文搜索、keyword 用于精确匹配与聚合、integer 用于数值、date 用于日期范围。text 字段会经过分词器切成词项再建倒排索引,keyword 字段则原样保存。

PUT /articles/_mapping
{
  "properties": {
    "title":   { "type": "text", "analyzer": "ik_max_word" },
    "tag":     { "type": "keyword" },
    "views":   { "type": "integer" },
    "created": { "type": "date" }
  }
}

中文分词是 ES 落地的第一道坎。默认 standard 分词器按空格与标点切,中文会被整段当作一个词,搜"后端"匹配不到"后端开发"。生产环境通常安装 IK 分词器,ik_max_word 做细粒度切词用于索引,ik_smart 做粗粒度切词用于查询,配合自定义词典收录业务专有词。

倒排索引与字段类型

倒排索引把"文档里有哪些词"反过来存成"词出现在哪些文档"。搜索"数据库",先查词表定位"数据库"对应的文档链表,再做评分与排序。ES 的 Lucene 底层用跳表与位图压缩文档链表,查询时跳过无关文档,这就是它比 LIKE 快几个数量级的原因。

字段类型的选择直接影响磁盘与查询。能用 keyword 就别用 text,能聚合的字段必须是 keyword 或 doc_values 支持的类型;数值字段尽量用整数与浮点,避免字符串数字;object 与 nested 处理嵌套文档,nested 的数组查询语义才正确但性能开销大,能拍平就拍平。

# 典型电商商品映射片段
"sku":   { "type": "keyword" },
"name":  { "type": "text", "analyzer": "ik_max_word" },
"price": { "type": "double" },
"stock": { "type": "integer" }

查询 DSL 实战

ES 查询 DSL 是一套 JSON 语法,核心分两类:叶子查询负责匹配,复合查询负责组合。match 做全文匹配,term 做精确匹配,range 做范围过滤,bool 用 must、should、must_not 组合多个条件,是日常最常用的容器。

{
  "query": {
    "bool": {
      "must":   [ { "match": { "title": "后端 微服务" } } ],
      "filter": [
        { "term":  { "status": "published" } },
        { "range": { "views": { "gte": 1000 } } }
      ]
    }
  },
  "sort": [ { "created": "desc" } ],
  "from": 0, "size": 20
}

filter 子句不参与评分但参与过滤,能被缓存,把高频过滤条件放 filter 里能显著提速。排序字段要用 keyword 或开启 doc_values,避免对 text 字段排序;深分页用 search_after 而不是大 from,否则内存会爆。

聚合与分析

聚合让 ES 在检索之外还能做统计。terms 聚合按字段分组计数,类似 SQL 的 group by;date_histogram 按时间桶聚合,是日志分析的主力;metrics 聚合计算 min、max、avg、sum 与 percentiles。聚合的字段必须是 keyword 或数值,text 字段要先加 keyword 子字段。

{
  "size": 0,
  "aggs": {
    "by_status": {
      "terms": { "field": "status.keyword", "size": 10 },
      "aggs": { "avg_views": { "avg": { "field": "views" } } }
    }
  }
}

聚合的精度与性能要权衡:terms 默认只取前几个桶,deep 聚合改用 composite 游标分页;date_histogram 的时间间隔按数据量选分钟、小时还是天。聚合是把双刃剑,写得好是分析利器,写不好就是集群杀手,监控聚合查询的耗时与内存占用很有必要。

性能优化

ES 性能优化从模型、查询、集群三层入手。模型层:字段能精简就精简,关闭不需要的 _source 字段的加载,映射里禁用不用的聚合与 doc_values;查询层:优先 filter 缓存,控制 from 深度用 search_after,避免前缀通配查询,能精确匹配就不全文检索。

集群层:分片数在创建索引时定死,主分片按数据量与节点数规划,副本数按读压力调整;写入端用 bulk 批量提交,合并小的请求;刷新间隔 refresh_interval 从默认 1 秒调到 30 秒,写入吞吐能提升数倍,代价是搜索可见延迟变长,适合日志场景。

三层优化速查
  • 模型精简字段、控制映射、减少存储。
  • 查询filter 缓存、search_after、避免通配。
  • 集群合理分片、bulk 写入、放宽刷新。