这篇文章不是单纯整理 ES / OpenSearch 的概念,也不是堆 DSL 语法,而是从一个后端开发者的角度,梳理我对搜索系统的理解。
我会按这条主线来写:
它解决什么问题 → 为什么需要它 → 怎么实现 → 有什么坑 → 怎么优化 → 在项目里怎么用
看完之后,至少能回答这几个问题:
- 为什么不能一直用 MySQL 顶搜索?
- ES / OpenSearch 到底适合解决什么问题?
text和keyword怎么选?query和filter为什么要分开?- 深分页为什么慢?
- 索引设计错了怎么平滑升级?
- 在资讯聚合、BI、AI 检索场景里怎么落地?
1. 为什么很多业务会引入 ES / OpenSearch
一开始做系统的时候,很多查询直接用 MySQL 就够了。
比如:
- 根据 ID 查详情
- 根据状态筛选列表
- 根据时间排序
- 简单模糊搜索一下标题
这些场景用 MySQL 没什么问题。
但当业务变成下面这样时,MySQL 就开始吃力了:
- 用户想按关键词搜索标题、摘要、正文
- 搜索结果要按相关性排序
- 还要叠加来源、分类、语言、时间范围等筛选条件
- 页面不只返回列表,还要展示热门分类、热门来源、趋势统计
- 数据量越来越大,搜索体验不能明显变慢
这时候问题就变了。
它已经不是简单的“查数据”,而是:
从大量文本和结构化字段中,快速找到最相关的一批结果,并且支持筛选、排序和分析。
这正是 ES / OpenSearch 更适合解决的问题。
所以我现在更倾向于把 ES / OpenSearch 理解成:
业务系统里的检索与分析层。
它不是用来替代 MySQL 的,而是把 MySQL 不擅长的全文检索、相关性排序、复杂筛选和聚合分析拆出来,交给更合适的系统处理。
2. ES / OpenSearch 到底解决了什么问题
2.1 解决全文检索问题
MySQL 也能做模糊查询,比如:
select * from news where title like '%OpenAI%';
小数据量下没问题,但数据量一大,这种查询就很难支撑复杂搜索体验。
因为它本质上不适合做大规模全文检索。
ES / OpenSearch 的核心优势是倒排索引。
简单理解就是:
- MySQL 更像是:文档 → 字段内容
- 倒排索引更像是:词 → 哪些文档包含这个词
比如有几篇文章:
doc1: OpenAI releases new model
doc2: Google updates AI search
doc3: OpenAI policy changes
倒排索引大概会记录成:
OpenAI -> doc1, doc3
AI -> doc2
policy -> doc3
search -> doc2
这样用户搜索 OpenAI 时,就不用一篇篇扫正文,而是直接找到包含这个词的文档列表。
这就是 ES / OpenSearch 做全文检索快的基础。
2.2 解决相关性排序问题
搜索不是“只要包含关键词就行”。
比如用户搜:
OpenAI policy
有几种情况:
- 标题里命中 OpenAI policy
- 摘要里命中 OpenAI policy
- 正文深处出现过一次 OpenAI
- 分类标签是 AI,但正文不相关
这些结果的相关性肯定不一样。
所以搜索系统不能只判断“有没有命中”,还要判断“命中得好不好”。
在实际业务里,通常会给不同字段设置不同权重:
- 标题命中更重要
- 摘要命中其次
- 正文命中权重低一点
比如:
{
"multi_match": {
"query": "OpenAI policy",
"fields": ["title^3", "summary^2", "content"]
}
}
这里的意思就是:
title^3:标题权重更高summary^2:摘要次之content:正文正常权重
这样结果会更符合用户预期。
2.3 解决组合筛选问题
很多搜索不是只输入一个关键词。
真实业务里,用户通常会同时加很多条件:
- 只看英文资讯
- 只看某几个来源
- 只看最近 30 天
- 只看某个分类
- 按发布时间倒序
这时候 ES / OpenSearch 不只是做全文检索,还要做结构化筛选。
典型查询会长这样:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "OpenAI policy",
"fields": ["title^3", "summary^2", "content"]
}
}
],
"filter": [
{ "term": { "language": "en" } },
{ "terms": { "source": ["Reuters", "TechCrunch"] } },
{ "range": { "published_at": { "gte": "now-30d" } } }
]
}
}
}
这里要注意一个很重要的点:
- 关键词搜索放在
must - 语言、来源、时间这些筛选条件放在
filter
因为关键词搜索需要算相关性,而语言、来源、时间只是判断是否满足条件,不需要参与打分。
2.4 解决聚合分析问题
很多人一开始以为 ES / OpenSearch 只是搜索工具,其实它还有很强的分析能力。
比如在资讯平台里,用户搜了一个关键词之后,页面除了展示文章列表,还可以展示:
- 各来源分别有多少篇
- 各分类分别有多少篇
- 最近 7 天每天有多少篇
- 哪些搜索词无结果
- 哪些分类点击率更高
这些都可以通过聚合来做。
比如统计不同来源的数量:
{
"size": 0,
"aggs": {
"source_count": {
"terms": {
"field": "source"
}
}
}
}
这类能力对内容平台、日志分析、BI、埋点分析都很有用。
所以我理解 ES / OpenSearch 时,不会只把它当成“搜索框后面的组件”,而是把它当成一个检索与分析层。
3. 为什么不是继续用 MySQL
MySQL 很重要,但它不是万能的。
在业务系统里,我更倾向于这样分工:
| 层 | 主要职责 |
|---|---|
| MySQL | 源数据、事务、强一致更新 |
| ES / OpenSearch | 全文搜索、筛选、排序、聚合分析 |
| Redis | 缓存、计数、限流、热点数据 |
| MQ / Celery | 异步同步、削峰、任务流转 |
MySQL 的优势是事务和结构化数据管理。
但如果让它同时承担:
- 大文本全文检索
- 复杂相关性排序
- 多字段搜索
- 大量聚合分析
- 高并发搜索请求
系统会越来越难维护。
所以引入 ES / OpenSearch 的核心原因不是“它更高级”,而是:
让不同系统做自己擅长的事。
MySQL 继续做源数据层,ES / OpenSearch 做检索和分析层,这样系统边界更清楚,后面优化也更有方向。
4. ES / OpenSearch 的核心实现主线
我理解 ES / OpenSearch 时,会抓这几件事:
- 倒排索引
- mapping 设计
text和keywordquery和filter- 排序、分页、聚合
- reindex 和 alias
这些比单纯背 DSL 更重要。
5. 倒排索引:为什么它适合搜索
倒排索引是 ES / OpenSearch 的基础。
普通数据库更多是围绕“记录”组织数据,而倒排索引是围绕“词”组织数据。
比如用户搜索一个关键词,ES 不需要从第一篇文章扫到最后一篇,而是直接通过关键词找到对应文档。
这也是为什么 ES / OpenSearch 很适合:
- 文章搜索
- 商品搜索
- 日志检索
- 工单搜索
- 知识库检索
- RAG 检索召回
不过倒排索引也带来一个点:
写入后不是绝对实时可见,而是近实时。
也就是说,文档写入之后,通常需要经过 refresh 才能被搜索到。
这点在业务设计时要注意。
如果是订单支付这种强一致场景,不能完全依赖 ES;但如果是资讯搜索、日志检索、知识库检索,近实时一般可以接受。
6. mapping 设计:搜索系统的第一步
很多 ES / OpenSearch 的问题,不是查询写得不好,而是 mapping 一开始就设计错了。
mapping 可以理解成 ES 里的“字段设计”。
它决定了:
- 字段是什么类型
- 是否分词
- 能不能做精确匹配
- 能不能排序
- 能不能聚合
- 查询时怎么匹配
所以我的理解是:
mapping 决定上限,DSL 只是发挥这个上限。
如果字段类型错了,后面查询怎么调都会很别扭。
7. text 和 keyword:最容易混,也最重要
这是 ES / OpenSearch 里最基础、也最容易踩坑的点。
7.1 text 适合全文检索
text 字段会被分词,适合:
- 标题
- 摘要
- 正文
- 描述
- 评论内容
比如:
"title": {
"type": "text"
}
用户搜索一个词时,ES 会根据分词后的结果去匹配文档。
7.2 keyword 适合精确匹配、排序和聚合
keyword 不会被分词,适合:
- ID
- 状态
- 分类
- 标签
- 来源
- 语言
- 用户名
- 邮箱
- 业务编码
比如:
"source": {
"type": "keyword"
}
这样就可以做:
- 精确筛选
- 排序
- 聚合统计
比如按来源统计数量,source 就应该是 keyword。
8. 一个字段有时需要 text + keyword
实际业务里,一个字段可能既要全文搜索,又要精确匹配。
比如资讯标题 title:
- 用户搜索时,希望标题参与全文检索
- 但有时也希望按完整标题去重、聚合或排序
这时可以用 multi-fields:
"title": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
这样:
title用来全文搜索title.raw用来精确匹配、排序或聚合
这类设计在搜索系统里很常见。
9. query 和 filter:一个管相关性,一个管筛选
这是查询调优里非常重要的一点。
我自己的理解是:
query关心:这个文档和搜索词有多相关filter关心:这个文档是否满足条件
比如用户搜索:
OpenAI policy
这个关键词匹配需要算相关性,所以放在 query。
但下面这些条件不需要算相关性:
- language = en
- category = policy
- published_at >= 最近 30 天
- source in Reuters / TechCrunch
这些条件只需要判断满足不满足,所以更适合放 filter。
错误写法是把所有条件都丢进 query,导致每个条件都参与打分,结果查询复杂,解释也混乱。
更合理的写法是:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "OpenAI policy",
"fields": ["title^3", "summary^2", "content"]
}
}
],
"filter": [
{ "term": { "language": "en" } },
{ "term": { "category": "policy" } },
{ "range": { "published_at": { "gte": "now-30d" } } }
]
}
}
}
这个设计思路很重要。
面试里可以直接说:
关键词检索我放在 query 里做相关性计算,状态、分类、时间这类结构化条件放在 filter 里做筛选,避免无意义打分。
这句话非常实用。
10. 排序:不只是按时间倒序
很多业务搜索结果不是简单按时间排序。
常见排序方式有几种:
10.1 按相关性排序
默认搜索通常会按 _score 排。
这适合用户主动搜索关键词的场景。
10.2 按时间排序
资讯、日志、动态类场景经常需要按发布时间排序。
"sort": [
{ "published_at": "desc" }
]
10.3 相关性 + 时间排序
实际业务里,更常见的是二者结合:
"sort": [
"_score",
{ "published_at": "desc" }
]
这样既考虑搜索相关性,也考虑内容新鲜度。
10.4 按热度排序
比如点击量、浏览量、收藏量、转发量,可以形成一个 hot_score。
但热度排序要谨慎。
如果完全按热度,可能导致旧内容长期霸榜;如果完全按时间,可能导致质量不稳定。
所以真实系统里经常会做综合排序:
- 相关性
- 发布时间
- 热度
- 来源权重
- 内容质量分
这已经接近搜索排序策略了。
11. 分页:浅分页和深分页要分开
普通列表页一般用:
{
"from": 0,
"size": 20
}
第一页没问题,前几页也没问题。
但如果用户一直往后翻,比如第 1000 页,就会变慢。
原因是 ES 需要先找到前面大量结果,再跳过它们,最后返回当前页。
也就是说:
from 越大,跳过的结果越多,成本越高。
所以一般可以这样设计:
- 普通列表浅分页:
from + size - 深分页 / 无限滚动:
search_after - 后台导出大量数据:用 scroll 或其他批处理方案
对于面试来说,记住一句就够了:
浅分页可以用 from/size,深分页不要硬翻,应该用 search_after 这类游标式方案。
12. 聚合:搜索系统里的分析能力
ES / OpenSearch 的聚合能力很适合做业务分析。
比如资讯平台里可以做:
- 热门来源
- 热门分类
- 每日资讯数量趋势
- 搜索结果分布
- 用户搜索无结果率
- 不同分类点击率
比如按分类聚合:
{
"size": 0,
"aggs": {
"category_count": {
"terms": {
"field": "category"
}
}
}
}
这里 size: 0 表示不返回具体文档,只返回聚合结果。
这类查询很适合用于:
- 数据看板
- BI 分析
- 埋点指标统计
- 内容运营分析
不过聚合也不是越多越好。
聚合通常比普通查询更消耗 CPU 和内存,所以要围绕业务真正需要的指标设计,不要为了炫技堆很多聚合。
13. 索引升级:reindex + alias
ES / OpenSearch 有一个很重要的工程问题:
mapping 设计错了怎么办?
比如一开始把 source 建成了 text,后来发现它应该是 keyword,因为要做精确筛选和聚合。
这时通常不能简单原地改字段类型。
更常见的做法是:
- 新建一个索引,比如
news_v2 - 在新索引里设计正确的 mapping
- 把旧索引
news_v1的数据 reindex 到新索引 - 用 alias 把线上查询从
news_v1切到news_v2
大概是这样:
news_v1 -> 旧索引
news_v2 -> 新索引
news -> alias,对外统一访问
应用层只访问 news 这个 alias。
切换时把 alias 从 news_v1 指向 news_v2。
这样好处是:
- 应用层不用改索引名
- 可以提前构建新索引
- 切换过程更平滑
- 出问题时也更容易回滚
这就是从“会用 ES”到“能线上落地”的区别。
14. 做 ES / OpenSearch 最容易踩的坑
14.1 把所有字符串都建成 text
这是新手很容易犯的错。
比如:
"source": {
"type": "text"
}
如果 source 是新闻来源,比如 Reuters、TechCrunch,它更适合是 keyword。
否则后面做精确筛选、排序、聚合都会很麻烦。
14.2 把所有条件都写进 query
很多筛选条件不需要相关性评分。
比如:
- 状态
- 分类
- 来源
- 语言
- 时间范围
这些更适合放到 filter。
否则会让查询逻辑变复杂,也可能影响性能。
14.3 过度依赖 dynamic mapping
dynamic mapping 很方便,字段来了自动推断类型。
但线上系统里,如果完全依赖它,容易出现:
- 字段类型不符合预期
- 字段数量失控
- 垃圾字段进入索引
- 查询和聚合不稳定
所以核心索引最好提前设计 mapping。
14.4 深分页一直用 from/size
小页数没问题,大页数会越来越慢。
如果业务上需要无限滚动、深翻页,应该考虑 search_after。
14.5 只关注“能搜到”,不关注“搜得准”
很多搜索系统上线后,最常见的问题不是查不出来,而是结果排序不合理。
比如:
- 标题命中和正文命中权重一样
- 新内容和旧内容没有区分
- 高质量来源和低质量来源没有区分
- 搜索词和业务标签没有结合
所以搜索系统不是“写个 match 就完事”,还要考虑字段权重、排序策略和业务目标。
14.6 mapping 设计错了还想硬改
字段类型一旦设计错,后面会很麻烦。
所以更推荐标准流程:
新建索引 -> reindex -> alias 切换
这也是线上系统更稳妥的做法。
15. 搜索系统怎么优化
15.1 先优化索引设计,再优化 DSL
很多时候查询慢,不是因为 DSL 写得不够花,而是字段设计错了。
比如:
- 应该是
keyword的字段建成了text - 需要排序聚合的字段没有设计好
- 字段过多,mapping 失控
- 没有区分全文字段和结构化字段
所以第一步应该先看 mapping。
15.2 query 和 filter 分工清楚
搜索词走 query,结构化条件走 filter。
比如:
title / summary / content:走全文 querysource / category / language / time:走 filter
这能让查询逻辑更清楚,也避免无意义打分。
15.3 字段权重要符合业务直觉
一般来说:
- 标题命中 > 摘要命中 > 正文命中
- 新内容在资讯场景里通常更重要
- 权威来源可能需要更高权重
- 热度可以参与排序,但不能完全支配排序
搜索排序要服务业务,不只是技术问题。
15.4 分页方式要和场景匹配
普通列表页:
from + size
无限滚动或深分页:
search_after
大批量导出:
批处理方案
不同场景不要用同一套分页方式硬扛。
15.5 聚合围绕指标设计
聚合不是越多越好。
应该围绕业务指标设计,比如:
- PV / UV
- 点击率
- 无结果率
- 热门分类
- 热门来源
- 搜索词分布
这些指标能反过来帮助产品和运营判断:
- 用户在搜什么
- 哪些内容更受欢迎
- 哪些搜索没有满足用户需求
- 哪些分类需要补充内容
16. 面试里可以怎么讲
如果面试官问:
你项目里为什么用 ES / OpenSearch?
可以这样答:
在资讯聚合项目里,MySQL 主要负责源数据存储和业务更新,但用户搜索场景里需要全文检索、来源/分类/语言/时间筛选、相关性排序和聚合分析,这些不是 MySQL 最擅长的。所以我会把 ES / OpenSearch 作为单独的检索与分析层。索引设计上,标题、摘要、正文用 text,来源、分类、语言用 keyword,发布时间用 date;查询时关键词走 query,结构化条件走 filter;结果页除了返回列表,还可以返回来源和分类的聚合结果。
如果继续追问:
query 和 filter 为什么要分开?
可以答:
query 负责相关性,比如关键词在标题、摘要、正文里的匹配程度;filter 负责条件筛选,比如语言、分类、时间范围。这些条件不需要参与打分,放 filter 里更清晰,也能避免无意义的相关性计算。
如果再追问:
深分页怎么处理?
可以答:
普通列表页可以用 from/size,但深分页会越来越慢,因为需要跳过大量前面的结果。更深的翻页或者无限滚动,我会改成 search_after 这类游标式分页方案。
如果追问:
mapping 设计错了怎么办?
可以答:
一般不会直接硬改旧字段类型,而是新建一个正确 mapping 的新索引,通过 reindex 把数据迁过去,再用 alias 切换线上读写索引,这样对业务影响更小,也方便回滚。
17. 总结
学 ES / OpenSearch,最重要的不是一上来背很多 DSL。
我觉得更重要的是先建立这条主线:
业务为什么需要搜索和分析层?
哪些数据适合放进索引?
字段类型怎么设计?
查询里哪些条件要算相关性,哪些只是过滤?
排序、分页、聚合怎么支撑产品体验?
索引变更怎么平滑升级?
把这些问题想清楚之后,ES / OpenSearch 就不是一个孤立的中间件知识点,而是一套可以挂到真实项目里的工程能力。
对内容平台来说,它支撑的是搜索体验和内容分析。
对 BI 系统来说,它可以补充搜索式数据发现和低延迟聚合能力。
对 AI 应用来说,它也可以作为 RAG / Agent 检索层的一部分,和向量检索、结构化过滤一起配合。
所以我最后对它的理解是:
ES / OpenSearch 不是替代数据库,而是在业务系统中承担检索与分析职责。真正的重点不是会写几个查询语法,而是能不能从业务目标出发,设计好索引、查询、分页、聚合和升级方案。