新闻详情

新闻详情

首页 / 资讯中心 / 详情

从 MySQL 的 LIKE 到 ES 倒排索引:一次把全文检索和混合检索讲透

发布时间:2026/9/28 4:37:30来源:尧图网络
从 MySQL 的 LIKE 到 ES 倒排索引:一次把全文检索和混合检索讲透
写在前面做后端的朋友大概率都遇到过这个场景产品经理跑过来跟你说“能不能给文章加个搜索功能就按内容搜跟百度那样。”你第一反应可能是SELECT * FROM article WHERE content LIKE %关键词%;跑一下几条数据的时候挺快等到表里躺了几十万条、content 字段动辄几百字的时候你会发现——查询直接奔着秒级去了稍微并发一下数据库 CPU 拉满。于是你就被逼着去了解 ElasticSearch。这篇文章不打算把 ES 讲成大部头教材我只想带你走完这条线为什么 MySQL 扛不住 → 倒排索引到底是怎么回事 → ES 怎么上手 → 最后怎么和向量检索组合成混合检索落到 RAG / Agentic RAG 场景里。读完你应该能把一个“关键词搜索 语义搜索”的服务真正跑起来。一、MySQL 为什么不适合全文检索先说清楚一件事MySQL 不是不好它是被用错了地方。MySQL 是关系型数据库它的强项是什么按字段精确查找WHERE id 1表关联JOIN事务、一致性、约束这些都是它吃饭的本事快得一批。但到了“按文本内容模糊搜索”这件事上它就很难受了。原因在于它的存储和检索方式MySQL 是按行存储的一行数据是一个完整单位。当你用LIKE %关键词%时它没有任何捷径可走只能逐行扫描、逐字匹配。说白了就是一条一条看过去看完才知道有没有。数据量越大、文本越长、模糊匹配越复杂它就越慢。这不是调优能解决的问题这是数据结构决定的先天缺陷。所以结论很直接大范围的关键词/文本检索别用LIKE交给专门的搜索引擎来做。这个“专门的搜索引擎”就是 ElasticSearch。二、核心差异正向索引 vs 倒排索引ES 相比 MySQL最大的杀手锏不是“它是个新数据库”而是它底层的倒排索引Inverted Index机制。我们用一个具体的例子感受一下。假设有这样一段文本可以在浏览器中运行机器学习模型支持图像、文本和声音等多种应用场景MySQL 的思路正向索引是文档 → 关键词存的是“这句话”你搜的时候再去这句话里翻。ES 的思路倒排索引是关键词 → 文档它会先对text类型字段做分词tokenization把句子拆成一个个独立词条可以 / 浏览器 / 机器学习 / 模型 / 图像 / 文本 / 声音 / 应用场景...然后以词条为核心反向记录“哪些文档包含这个词”浏览器 → [文档1, 文档2] 机器学习 → [文档1, 文档5] 图像 → [文档1]用户输入关键词时ES 只需要拿着这个词去词条表里秒查直接拿到文档 ID 列表完全不需要全表遍历。这就是为什么 ES 能在海量文本下做到毫秒级全文检索。一句话记忆MySQL 是“给你一本书一页页翻着找词”ES 是“先给你建好目录直接翻到那一页”。三、先把环境跑起来docker-compose 三件套理论说完了动手。ES 用 Docker 起是最省事的。先明确几个概念别被 Docker 的名词吓到镜像 image代码 环境依赖打包好的“模板”容器 container镜像运行起来之后的实例compose把多个镜像编排到一起启动配置文件就是docker-compose.yml我们要起两个东西服务端口作用Elasticsearch9200存索引、提供检索 APIKibana5601可视化操作 ES类似 phpMyAdmin 之于 MySQLdocker-compose.yml大概长这样version: 3.8 services: # Elasticsearch 最新稳定版8.17.0 es: build: ./elasticsearch container_name: es-dev ports: - 9200:9200 # ES 对外提供服务的端口 environment: - discovery.typesingle-node # 单节点运行开发环境 - xpack.security.enabledfalse # 关闭安全认证免密码访问 - xpack.security.http.ssl.enabledfalse # 关闭 HTTPS 加密 - xpack.security.transport.ssl.enabledfalse # 关闭节点传输加密 - ES_JAVA_OPTS-Xms512m -Xmx512m # JVM 内存配置避免占用过高 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/es:/usr/share/elasticsearch/data restart: always # Kibana 最新稳定版8.17.0必须与 ES 版本完全一致 kibana: image: kibana:8.17.0 container_name: kibana-dev ports: - 5601:5601 # Kibana 网页控制台端口 environment: - ELASTICSEARCH_HOSTShttp://es:9200 # 连接 ES 容器内部地址 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/kibana:/usr/share/kibana/data restart: always depends_on: - es # 等待 ES 启动完成后再启动 Kibana networks: default: name: common-network启动命令就一行docker compose up -d拆解一下这条命令docker compose在当前目录找docker-compose.ymlup把服务启动起来-d后台运行detached起来之后浏览器打开http://localhost:5601就能看到 Kibana后面所有操作都可以在它的 Dev Tools 里直接敲。四、ES 请求心智模型方法、路径、参数、Body很多同学第一次看 ES 命令会懵为什么有的用GET有的用POST有的用PUT/_doc、/_search、/_mapping这些路径到底怎么拼Kibana 里能跑换成 curl 或 Java 客户端怎么写POST和PUT到底有什么区别为什么有时候查不到刚写入的数据这一节就把 ES 的RESTful 请求方式彻底拆开。4.1 ES 的本质一切皆 HTTP 请求ES 对外暴露的是一套RESTful JSON API。你可以把它理解成一个 HTTP 服务HTTP 方法决定“做什么”路径决定“操作谁”查询参数决定“细节”请求体里的 DSL 决定“怎么做”。一个完整的 ES 请求由四部分组成HTTP 方法 /路径?查询参数 { 请求体 JSON }例如POST /article/_search?pretty { query: { match: { title: Elasticsearch } } }拆开看部分内容作用HTTP 方法POST提交一次搜索请求路径/article/_search对 article 索引执行搜索查询参数?pretty返回结果格式化请求体query.match...查询 DSLKibana Dev Tools 里之所以能省略http://localhost:9200是因为它已经帮你连好了 ES。换成 curl就必须补全地址和请求头。4.2 HTTP 方法速查GET / POST / PUT / DELETE / HEADES 里最常用的 HTTP 方法就这几个HTTP 方法语义ES 典型用途是否幂等GET读取查询文档、搜索、查看 mapping/settings、_cat是POST提交/执行自动生成 ID 新增文档、_update、_search、_bulk、_delete_by_query通常不幂等PUT创建/覆盖创建索引、指定 ID 写入文档、更新 mapping/settings是DELETE删除删除索引、删除文档是HEAD探测判断索引/文档是否存在是一句话记忆GET 查POST 提交PUT 覆盖DELETE 删HEAD 探。GET读操作但也能带 bodyGET在 ES 里最常用来查GET /article/_doc/1001 GET /article/_mapping GET /article/_settings GET /_cat/indices?v GET /article/_search { query: { match_all: {} } }注意ES 是支持 GET 带请求体的所以上面GET /article/_search带 JSON 在 Kibana 里能跑。但这里有个坑虽然 HTTP 规范没有完全禁止 GET 带 body但很多代理、网关、HTTP 客户端会忽略 GET 的 body。所以复杂搜索建议用 POST兼容性更好。也就是说下面两种写法在 ES 里都合法GET /article/_search { query: { match: { title: ES } } }POST /article/_search { query: { match: { title: ES } } }生产环境更推荐POST /article/_search。POST新增、更新、搜索、批量POST是 ES 里最灵活的方法常见用途POST /article/_doc # 自动生成 ID 新增文档 POST /article/_update/1001 # 部分更新文档 POST /article/_search # 搜索 POST /_bulk # 批量操作 POST /article/_delete_by_query # 按条件删除为什么新增文档有时用POST有时用PUT看下面。PUT指定 ID 写入幂等PUT通常表示“创建一个确定 ID 的资源”PUT /article { mappings: { ... } }创建索引article。PUT /article/_doc/1001 { title: ES 入门, content: 倒排索引... }指定文档 ID 为1001。PUT的特点是幂等你执行一次和一百次结果都是 ID 为1001的文档被写成同样的内容。但注意PUT /article/_doc/1001是全量覆盖。如果你原来文档有title、content、author这次只传了title那么其他字段会丢失。想部分更新用POST /article/_update/1001。DELETE删除索引或文档DELETE /article/_doc/1001 # 删除单条文档 DELETE /article # 删除整个索引删除也是幂等的删完之后再删一次会返回not_found但不会产生额外副作用。HEAD只关心存在不存在HEAD /article HEAD /article/_doc/1001HEAD不返回 body只看 HTTP 状态码200存在404不存在适合在程序里做存在性判断省带宽。4.3 ES 路径结构索引、文档、搜索、映射ES 的路径其实很有规律记住几个核心模板就行/index # 索引本身 /index/_doc/id # 单条文档 /index/_search # 搜索 /index/_mapping # 映射 /index/_settings # 设置 /_cat/... # 集群/索引查看 /_bulk # 批量 /_mget # 多文档查询举几个例子GET /article/_doc/1001 GET /article/_search GET /article/_mapping GET /article/_settings GET /_cat/indices?v POST /_bulk GET /_mget在 ES 7.x 之前路径里还有“类型 type”的概念比如/index/type/id。7.x 之后 type 被废弃统一用_docPUT /article/_doc/1001你可以把_doc理解成固定端点不用再纠结 type。4.4 HTTP 状态码看返回值判断结果ES 返回的 HTTP 状态码很有用状态码含义常见场景200 OK成功查询、更新、删除成功201 Created创建成功新建索引、写入新文档400 Bad Request请求错误DSL 语法错误、字段类型不匹配401 Unauthorized未认证没带用户名密码403 Forbidden无权限用户权限不足404 Not Found不存在索引/文档不存在409 Conflict冲突版本冲突、索引已存在413 Payload Too Large请求体太大单次 bulk 太大429 Too Many Requests请求过多限流、线程池满500 Internal Server Error服务端错误分片失败、脚本异常程序里不要只看 bodyHTTP 状态码是第一道判断。五、索引操作建表、映射、设置5.1 索引Index≈ MySQL 的表ES 里没有“表”的概念对应的叫索引Index。先看看当前有哪些索引GET /_cat/indices?vhhealth,status,index,docs.count创建索引的时候需要定义mappings——这玩意儿就相当于 MySQL 的建表 schema。PUT /article { settings: { number_of_shards: 1, number_of_replicas: 0 }, mappings: { properties: { title: { type: text }, content: { type: text }, author: { type: keyword }, createTime: { type: date }, viewCount: { type: integer } } } }这里有一个新手最容易踩的坑也是 ES 字段类型设计的核心类型行为适用场景text会分词拆成词条再建索引标题、正文等内容字段keyword不分词整体作为一个词条精确匹配作者、状态、标签、ID看上面的定义title和content用text因为你要按关键词搜author用keyword因为你要的是“作者恰好等于 AI开发”这种精确匹配。金句text负责“全文检索”keyword负责“精确过滤”两者搭配才是完整的搜索体验。5.2 查看映射与设置GET /article/_mapping # 查看字段结构 GET /article/_settings # 查看索引配置5.3 判断索引是否存在HEAD /article返回200表示存在404表示不存在。5.4 新增字段PUT /article/_mapping { properties: { status: { type: keyword } } }注意ES 不支持直接修改已有字段类型只能新增字段。要改类型一般要重建索引 reindex。5.5 修改设置PUT /article/_settings { number_of_replicas: 1 }5.6 删除索引DELETE /article删库慎用。5.7 关闭 / 打开索引POST /article/_close POST /article/_open5.8 别名操作POST /_aliases { actions: [ { add: { index: article_v1, alias: article } } ] }别名的好处是重建索引时应用层不用改代码只切别名。六、文档操作新增、查询、更新、删除6.1 新增自动 ID vs 指定 ID自动生成 IDPOST /article/_doc { title: Elasticsearch 全文检索入门, content: ES 基于倒排索引与 BM25 实现全文索引适用于文本检索场景, author: 后端开发, createTime: 2026-09-20, viewCount: 120 }指定 IDPUT /article/_doc/1001 { title: Elasticsearch 全文检索入门, content: ES 基于倒排索引与 BM25 实现全文索引适用于文本检索场景, author: 后端开发, createTime: 2026-09-20, viewCount: 120 }区别操作方法路径ID幂等自动 IDPOST/_docES 生成否指定 IDPUT/_doc/1001手动指定是6.2 查询单条文档GET /article/_doc/10016.3 判断文档是否存在HEAD /article/_doc/10016.4 部分更新POST /article/_update/1001 { doc: { viewCount: 121 } }只会更新viewCount其他字段不变。也可以用脚本更新POST /article/_update/1001 { script: { source: ctx._source.viewCount params.inc, params: { inc: 1 } } }6.5 删除文档DELETE /article/_doc/10016.6 多文档查询 _mgetGET /_mget { docs: [ { _index: article, _id: 1001 }, { _index: article, _id: 1002 } ] }也可以指定索引GET /article/_mget { docs: [ { _id: 1001 }, { _id: 1002 } ] }七、搜索操作match / term / multi_match / bool搜索是 ES 最核心的操作。两种方式GETGET /article/_search { query: { match: { title: ES } } }POSTPOST /article/_search { query: { match: { title: ES } } }前面说过复杂 DSL 推荐POST。7.1 URI 查询简单场景GET /article/_search?qtitle:检索size10适合临时调试不适合复杂业务。7.2 match —— 会分词GET /article/_search { query: { match: { content: 全文检索 } } }match会把你输入的内容也分词然后按词条去匹配。适合text字段。7.3 term —— 不分词精确匹配GET /article/_search { query: { term: { author: AI开发 } } }term不做任何分词拿整个字符串去词条表里精确查。专门用于keyword字段。记住这条规则match配textterm配keyword配错了就是查不出结果。7.4 multi_match —— 多字段匹配GET /article/_search { query: { multi_match: { query: 检索, fields: [title, content] } } }一次查多个字段用户搜“检索”的时候标题或正文命中都算非常实用。7.5 bool 查询must / filter / should / must_notPOST /article/_search { query: { bool: { must: [ { match: { content: 检索 } } ], filter: [ { term: { author: AI开发 } } ], should: [ { match: { title: Elasticsearch } } ], must_not: [ { term: { status: deleted } } ] } }, _source: [title, author, createTime], from: 0, size: 10, sort: [ { createTime: desc } ], highlight: { fields: { content: {} } } }这段基本覆盖了日常搜索must必须匹配参与打分filter必须匹配不参与打分可缓存适合精确过滤should可选匹配命中加分must_not必须不匹配_source返回哪些字段from/size分页sort排序highlight高亮7.6 只返回部分字段省流量GET /article/_search { _source: [title, author], query: { match_all: {} } }_source就相当于 SQL 里的SELECT title, author返回大字段比如 content非常吃带宽按需指定。7.7 分页from/size 与深分页浅分页from: 0, size: 10深分页不建议用大from比如from: 100000性能很差。生产深分页用search_after PIT。7.8 聚合查询POST /article/_search { size: 0, aggs: { authors: { terms: { field: author, size: 10 } } } }size: 0表示不返回文档只要聚合结果。五、从 ES 到混合检索为什么单靠关键词还不够到这里你已经能搭起一个可用的全文检索服务了。但如果你在做 RAG检索增强生成或者 Agent只上 ES 会撞到一堵墙。先看两个典型问题问题一语义相近但字面不同ES 搜不到。用户搜“土豆怎么做”但你的文档里写的是“马铃薯的烹饪方法”。ES 基于词条“土豆”和“马铃薯”是两个完全不同的词条匹配不上。问题二纯语义检索向量检索也不完美。如果把文档灌进向量数据库比如 Milvus用 embedding 做相似度检索上面那个问题就解决了——因为“土豆”和“马铃薯”在语义空间里离得很近。但是反过来专业术语、精确实体、代号、ID这类东西纯语义检索经常“匹配不准”甚至“糊成一团”。举个例子用户明确要找“型号 X200”向量检索可能给你返回一堆“X100、X300、X500”语义都很像但全都不是他要的。于是答案就清楚了关键词检索ES 语义检索Milvus 混合检索Hybrid Search六、混合检索架构长什么样大致流程┌─────────────────┐ 用户 Query ────┬──▶│ ES 关键词检索 │──▶ 命中结果 A │ └─────────────────┘ │ │ ┌─────────────────┐ └──▶│ Milvus 向量检索 │──▶ 命中结果 B └─────────────────┘ │ ▼ ┌─────────────────┐ │ 模型统一融合 │──▶ 最终 Top-K │ (RRF / rerank) │ └─────────────────┘ │ ▼ 交给 LLM 生成关键点两路并行ES 走关键词Milvus 走向量各自召回一批候选统一融合用 RRFReciprocal Rank Fusion或者重排模型reranker把两路结果合并、打分、排序再喂给 LLM融合后的 Top-K 作为上下文这样做的好处非常直接专业术语/精确实体靠 ES 保证不跑偏语义泛化靠向量保证不漏召。放到Agentic RAG的场景里会更明显。比如用 LangGraph 搭一个闭环 AgentAgent自主决策这次要不要检索检索的话用哪个工具Web Search / Milvus / ES拿到结果后判断信息够不够、效果好不好不够就换个检索方式再搜一轮比如用户问“型号 X200 的语义相似产品”Agent 第一轮可以直接走 ES 锁定 X200再用 Milvus 找语义相近的品类。工具组合是死的Agent 的判断是活的。真正要理解的是这个“决策—检索—评估—再检索”的闭环思路具体怎么实现一定是要贴着业务场景设计的。七、几个容易忽略的落地建议最后给几条实战经验都是踩过坑总结的1. 字段类型别乱设author、status、tag这类字段一律keyword。如果你既想精确匹配又想分词可以用textkeyword多字段title: { type: text, fields: { keyword: { type: keyword } } }2. 中文分词要用 IK 插件ES 默认分词器对中文是“一字一词”效果很差。上 IK 分词器用dockerRUN elasticsearch-plugin install --batch \ https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-8.17.0.zip然后在 mapping 里指定analyzer: ik_max_word。3. 别把 ES 当主数据库用ES 更像个“特种兵”——专门做关键词检索的。原始数据、事务、强一致还是老老实实放 MySQL。ES 里的数据是从 MySQL 同步过来的副本。4. Kibana 是开发期神器http://localhost:5601里的 Dev Tools 可以直接敲上面所有命令比 Postman 顺手太多。它之于 ES就像 phpMyAdmin 之于 MySQL。总结把这条线再串一遍MySQL 的 LIKE 慢是因为正向索引下只能全表逐行扫描ES 快是因为它用倒排索引把“文档 → 词”反过来了变成“词 → 文档”text分词、keyword不分词match配text、term配keyword单靠 ES 搜不了语义单靠向量搜不准实体所以要有混合检索混合检索的融合层才是准确率的关键RRF / rerank 都值得试Agentic RAG本质上是把“选哪种检索”这件事交给 Agent 自主决策一句话收尾关键词检索解决“找得到”语义检索解决“找得准”两者融合加上 Agent 的自主决策才是真正能落地的 RAG。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

三天用 Claude + Kimi 完成一个微信小程序,完整流程全记录(TaoToken 统一 Key 接入版) 2026/9/28 5:46:30

三天用 Claude + Kimi 完成一个微信小程序,完整流程全记录(TaoToken 统一 Key 接入版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
从零搭建本地 Hermes Agent:用 TaoToken 统一 Key 打通自动化智能应用部署 2026/9/28 5:46:30

从零搭建本地 Hermes Agent:用 TaoToken 统一 Key 打通自动化智能应用部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Spring Boot校园绿化管理系统开发全攻略:从需求拆解到部署答辩 2026/9/28 5:46:23

Spring Boot校园绿化管理系统开发全攻略:从需求拆解到部署答辩

想写这篇文章的念头,其实挺实在的——每年到了毕设季,后台总有人来问“Spring Boot到底能做什么项目”“校园管理类系统是不是太老套了”。我得说,校园绿化管理系统这个选题,看起来不花哨,但真正做下来你会发现&#x…

阅读更多 →
Python基于ARIMA时间序列的销量预测模型实战指南 2026/9/28 5:46:23

Python基于ARIMA时间序列的销量预测模型实战指南

简介:这是一套面向Python数据分析初学者、毕业设计与课程设计学生的ARIMA时间序列销量预测完整项目包,围绕平稳化处理、模型定阶、参数估计与模型检验展开,并采用每月上中下旬三次预测当月销量的策略,将月上旬与中旬实际销量作为先…

阅读更多 →
Vim 常用命令与快捷键实战指南:从入门到高效编辑 2026/9/28 5:46:23

Vim 常用命令与快捷键实战指南:从入门到高效编辑

我到现在都记得第一次被人按着在Linux服务器上改配置文件的场景:没有鼠标、没有图形界面,只有一个黑乎乎的终端,里面等着我的是一个叫 vim 的东西。旁边的老同事丢下一句“按 i 插入,按 Esc 返回,按 :wq 保存退出”就走…

阅读更多 →
全维度解析 AI 开发核心工具:智能编码 / 数据标注 / 模型训练平台配 TaoToken 统一 Key 通道 2026/9/28 5:46:23

全维度解析 AI 开发核心工具:智能编码 / 数据标注 / 模型训练平台配 TaoToken 统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉