【2025最新】为什么用ElasticSearch?和传统数据库MySQL与什么区别?

Elasticsearch 深度解析:从原理到实践


一、为什么选择 Elasticsearch?

数据模型
Elasticsearch 是基于文档的搜索引擎,它使用 JSON 文档来存储数据。 在 Elasticsearch 中,相关的数据通常存储在同一个文档中,而不是分散在多个表中。

**MySQL 是一个关系型数据库管理系统,它使用表、行和列的结构来组织数据。**数据通过外键关系分散在多个表中。

查询语言
Elasticsearch 使用 Query DSL(Domain Specific Language) ,这是一种非常灵活的查询语言,基于 JSON,支持全文搜索、复合查询、过滤以及聚合等。

MySQL 使用 SQL(Structured Query Language),这是一种强类型和非常成熟的语言,专门用于查询和管理关系数据库。

全文搜索
Elasticsearch 的核心功能是全文搜索。
它对数据进行索引时会自动建立全文搜索索引,使其在搜索大量文本数据时表现优异。

MySQL 虽然也提供了基本的全文搜索功能,但其主要设计目标是处理结构化数据的存储和查询,对全文搜索的支持不如 Elasticsearch 那样强大。

事务支持
Elasticsearch 不支持传统的 ACID(原子性、一致性、隔离性、持久性)事务。虽然它确保了单个文档操作的原子性,但不适用于跨多个文档的复杂事务。 虽然 Elasticsearch 不支持传统的事务,但是他是可以确保单个文档的更改(如创建、更新、删除)是原子性的 。这意味着任何文档级的操作都完整地成功或完整地失败,但不保证跨多个文档或操作的一致性。

MySQL 支持 ACID 事务,这使得它非常适合需要严格数据一致性的应用,如金融服务和其他商业数据处理。

支持乐观锁:

Elasticsearch 支持通过使用文档版本控制来实现乐观锁。

6.7之前。 在 ES 中,每个文档存储时都有一个 _version 字段,这个版本号在每次文档更新时自动增加。当我们执行更新、删除或者使用脚本处理文档时,可以指定这个版本号来确保正在操作的文档是预期中的版本。如果操作中的版本号与存储在索引中的文档版本号不一致,说明文档已被其他操作更改,当前操作将会失败。(CAS)

为什么_version在后续版本被抛弃

_version 机制是基于单一递增整数,它主要适用于简单的冲突检测,但在复杂的分布式系统中,仅依靠版本号可能无法准确地反映数据的历史和复制状态。尤其是在发生网络分区或节点故障时,仅凭 _version 可能导致数据丢失或过时的数据被错误地写入。

主要还是高并发和高可用 的环境下,单一的-version不足以处理因节点故障网络问题导致的多个副本之间的数据不一致问题。

然而if_seq_no 和 if_primary_term这两个参数可以更准确地确定操作是否应当被执行,可以跟踪每个文档变更的序列号主分片的期限。 这种机制帮助确保即使在集群状态发生变化(如分片重新分配到新的节点)的情况下,也不会应用基于过时副本的更新。它有效地防止了脑裂问题

6.7之后,使用 _version 关键字进行乐观锁已经被废弃了,替代方法是使用

if_seq_no ( 是一个递增的序列号,表示文档的每次修改**)和**

if_primary_term( 表示主分片的当前任期,每当主分片发生变化时,这个值会增加。

来指定版本。

1. 核心优势()
维度 Elasticsearch MySQL
数据模型 文档型(JSON),动态映射 关系型(严格Schema)
搜索能力 全文检索、模糊匹配、语义分析 仅支持精确查询和简单LIKE
扩展性 分布式架构,自动分片/副本 分库分表复杂
吞吐量 单集群支持每秒10万+查询 高并发写入更优
一致性 最终一致性(近实时) 强一致性(ACID)
Elasticsearch是一个开源的分布式搜索和分析引擎,主要适用于以下场景:

1:搜索引擎:用于快速检索文档、商品、新闻等。
2:日志分析:通过分析日志数据,帮助企业了解其业务的性能情况。
3:数据分析:帮助数据科学家和数据分析师进行数据分析,以获取有价值的信息。
4:商业智能:帮助企业制定数据驱动的决策,以实现商业上的成功。
5:实时监控:帮助企业实时监测系统性能、监控数据变化,以保证系统正常运行。
6:安全性:帮助企业保证数据的安全性,保证数据不被非法窃取。
7:应用程序开发:帮助开发人员开发基于搜索的应用程序,以增加用户体验。

Elasticsearch具有以下几个优势:
1:高性能 :Elasticsearch具有高性能的搜索和分析能力,其中涵盖了多种查询语言和数据结构。
2:可扩展性 :Elasticsearch是分布式的,可以通过增加节点数量扩展搜索和分析能力。
3:灵活性 :Elasticsearch支持多种数据类型,支持多种语言,支持动态映射,允许快速地调整模型以适应不同的需求。
4:实时分析 :Elasticsearch支持实时分析,可以对数据进行实时查询,这对于快速检索数据非常有用。
5:可靠性:Elasticsearch具有可靠性和高可用性,支持数据备份和恢复。

ES为什么块:

1:分布式存储 :Elasticsearch使用分布式存储技术,将数据存储在多个节点上,从而减少单个节点的压力,提高整体性能。
2:索引分片 :Elasticsearch把每个索引划分成多个分片,这样可以让查询操作并行化,从而提高查询速度。
3:全文索引 :Elasticsearch使用了高效的全文索引技术,把文档转化成可搜索的结构化数据,使得搜索操作快速高效。
4:倒排索引 :Elasticsearch支持倒排索引这种数据结构,倒排索引将文档中的每个词与该词出现在哪些文档中进行映射,并存储这些信息。当搜索请求发生时,ES可以快速查找包含所有搜索词的文档,从而返回结果。
5:索引优化 :Elasticsearch通过索引优化技术,可以使查询速度更快。例如,它支持索引覆盖、索引下推等优化技术,使得查询速度更快。
6:预存储结果 :Elasticsearch在插入数据时,对数据进行预处理,把结果预存储到索引中,从而在查询时不需要再重新计算,提高查询速度。
7:高效的查询引擎 :Elasticsearch使用了高效的查询引擎,支持各种类型的查询,并对复杂查询提供了优化策略,从而提高查询速度。
8:异步请求处理 :ES使用了异步请求处理机制,能够在请求到达时立即返回,避免长时间的等待,提高用户体验。
9:内存存储:ES使用了内存存储技术,能够在读写数据时大大减少磁盘访问次数,提高数据存储和查询效率。

我觉得最重要就是倒排索引了:它 用于快速搜索文档中的某个词汇。

假设我们有一个原始文档:

文档ID 文档内容
Doc1 "Elasticsearch is fast"
Doc2 "Redis is fast too"
Doc3 "Elasticsearch and Redis"

主要分为俩个过程:

  1. 分词(Tokenization)

    • Doc1 → ["elasticsearch", "is", "fast"]

    • Doc2 → ["redis", "is", "fast", "too"]

    • Doc3 → ["elasticsearch", "and", "redis"]

  1. 生成词项-文档映射
复制代码
Term            DocIDs (Postings List)
─────────────────────────────────────
"and"           → [Doc3]
"elasticsearch" → [Doc1, Doc3]
"fast"          → [Doc1, Doc2]
"is"            → [Doc1, Doc2]
"redis"         → [Doc2, Doc3]
"too"           → [Doc2]
3. 倒排索引的核心组件
组件 作用 示例
词项字典(Term Dictionary) 存储所有唯一词项,通常用 FST(有限状态转换器) 压缩存储 "elasticsearch", "redis"
倒排列表(Postings List) 记录包含该词项的文档ID及位置信息(支持快速跳表SkipList优化遍历) Doc1:[pos1,pos2], Doc3:[pos1]
词频(TF) 词项在文档中的出现次数(用于相关性评分) "fast"在Doc1中TF=1
文档频率(DF) 包含该词项的文档数(用于IDF计算) "elasti
倒排索引的优化技术
  1. 压缩存储

    • FST:压缩词项字典,减少内存占用。

    • Delta Encoding:对文档ID差值编码(如[100, 102, 105]→[100, +2, +3])。

  2. 跳表(SkipList)

    • 加速倒排列表的合并操作(如AND查询)。
  3. 多级索引

    • 内存中保留热点词项,磁盘存全量数据。
二、Elasticsearch 核心技术细节
1. 分词器(Analyzer)
分词器类型 功能说明 示例
Standard 默认分词器,按空格/标点切分 "Elasticsearch" → ["elasticsearch"]
IK Analyzer 中文分词(社区版+扩展词典) IK分词器有几种模式? * ik_smart:智能切分,粗粒度 * ik_max_word:最细切分,细粒度 IK分词器如何拓展词条?如何停用词条? * 利用config目录的IkAnalyzer.cfg.xml文件添加拓展词典和停用词典 * 在词典中添加拓展词条或者停用词条 "华为手机" → ["华为", "手机"]
Pinyin 中文转拼音搜索 "北京" → ["beijing", "bj"]
Whitespace 仅按空格切分 "hello world" → ["hello", "world"]
Keyword 不分词,整体作为Term "2023-08-15" → ["2023-08-15"]

自定义分词器

复制代码
PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_ik": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase"]
        }
      }
    }
  }
}
2. 底层原理
  • 倒排索引(Inverted Index)

    • Term → Document ID 的映射(如"手机" → [Doc1, Doc3])。

    • 通过 FST(Finite State Transducer) 压缩存储,减少内存占用。

  • 分片(Shard)机制

    • 索引自动拆分为多个分片(默认5个),分散到不同节点。

    • 每个分片有1个主副本和N个从副本(通过_settings调整)。

  • 近实时(NRT)

    • 数据写入后默认1秒(refresh_interval)可被搜索,通过 translog 保证持久化。
3. 查询高效的原因
  • 分布式计算

    • 查询并行发送到所有分片,结果聚合(Scatter-Gather模式)。
  • 缓存优化

    • Query Cache:缓存过滤条件结果。

    • Fielddata:文本字段启用内存缓存(慎用,易OOM)。

  • 列式存储(Doc Values)

    • 对排序、聚合字段预先构建磁盘数据结构,避免实时计算。
4. 增删改操作
  • 写入流程

    1. 请求发送到协调节点。

    2. 路由到对应分片的主副本。

    3. 写入Lucene内存Buffer和translog。

    4. 定期刷新(Refresh)生成新Segment(可搜索)。

    5. 后台合并(Merge)Segment优化存储。

  • 删除

    • 标记文档为deleted,Merge时物理删除。
  • 更新

    • 先删除旧文档,再写入新文档(版本号_version递增)。

三、关键问题解决方案
1. 深度分页优化
  • 问题from 10000, size 10 需遍历所有分片的10010条记录。在Elasticsearch中进行分页查询通常使用from和size参数。当我们对Elasticsearch发起一个带有分页参数的查询(如使用from和size参数)时,ES需要遍历所有匹配的文档直到达到指定的起始点(from),然后返回从这一点开始的size个文档。跟MySQL的深度分页原因差不多,

  • 方案

    • Search After :search_after 是 Elasticsearch 中用于实现深度分页的一种机制。与传统的分页方法(使用 from 和 size 参数)不同,search_after 允许你基于上一次查询的结果来获取下一批数据,这在处理大量数据时特别有效。

      在第一次查询时,你需要定义一个排序规则。不需要指定 search_after 参数:

      复制代码
      {
        "query": { "match_all": {} },
        "size": 10,
        "sort": [{"_id": "asc"}],//在第一次查询时,你需要定义一个排序规则。
        "search_after": [last_id]
      //第一次查询不要定义,在后续的查询中,使用上一次查询结果中最后一条记录的排序值
      //search_after包含timestamp和last_id 的值,对应上一次查询结果的最后一条记录。
      
      }

      search_after 可以有效解决深度分页问题,原因如下:
      避免重复处理数据: 与传统的分页方式不同,search_after 不需要处理每个分页请求中所有先前页面上的数据。这大大减少了处理数据的工作量。

      提高查询效率: 由于不需要重复计算和跳过大量先前页面上的数据,search_after 方法能显著提高查询效率,尤其是在访问数据集靠后部分的数据时。

      但是这个方案有一些局限,一方面需要有一个全局唯一的字段用来排序,另外虽然一次分页查询时不需要处理先前页面中的数据,但实际需要依赖上一个页面中的查询结果。

    • 适用于深度分页和大数据集的遍历。

    • Scroll API:Scroll API在Elasticsearch中的主要目的是为了能够遍历大量的数据,它通常用于数据导出或者进行大规模的数据分析。可以用于处理大量数据的深度分页问题。

    • 复制代码

      POST /_search/scroll { "scroll": "1m", "scroll_id": "DXF1ZXJ5QW..." }

      //如上方式初始化一个带有scroll参数的搜索请求。这个请求返回一个scroll ID,用于后续的滚动。Scroll参数指定了scroll的有效期,例如1m表示一分钟。

      //接下来就可以使用返回的scroll ID来获取下一批数据。每次请求也会更新scroll ID的有效期。

    • 业务妥协:限制最大页码(如只展示前100页)。

    • **避免重复排序:**在传统的分页方式中,每次分页请求都需要对所有匹配的数据进行排序,以确定分页的起点。Scroll避免了这种重复排序,因为它保持了一个游标。

    • 稳定视图: **Scroll提供了对数据的"稳定视图"。**当你开始一个scroll时,Elasticsearch会保持搜索时刻的数据快照,这意味着即使数据随后被修改,返回的结果仍然是一致的。

    • 减少资源消耗:

      由于不需要重复排序,Scroll**减少了对CPU和内存的消耗,**特别是对于大数据集。

    • Scroll非常适合于处理需要访问大量数据但不需要快速响应的场景,如数据导出、备份或大规模数据分析。

      但是,需要知道,使用Scroll API进行分页并不高效,因为你需要先获取所有前面页的数据。Scroll API主要用于遍历整个索引或大量数据,而不是用于快速访问特定页数的数据。

2. 数据一致性

第一种方法:双写一致性

对MySQL和ES进行双写,先更新数据库,再更新ES,放在一个事务里面,需要保证事务的一致性。有数据不一致的风险,比如MySQL写入成功,但是ES发生了写入成功但因为超时抛异常了就会出现数据不一致的情况。

第二种方法:使用消息队列MQ进行处理

在更新数据库时,发送一个消息到MQ中,然后数据库和ES各设置一个监听者,监听消息之后各自去做数据变更,如果失败了就基于消息的重试在重新执行。

好处是进行了异步解耦,性能稍后,但是有延迟

第三种方法:定时扫描MySQL

如果ES中的数据变更的实时性要求不高,可以考虑定时任务扫表来批量更新ES。

这个方案优点是没有侵入性,数据库的写操作处不需要改代码。

缺点是实时性很差,并且轮询可能存在性能问题、效率问题以及给数据库带来压力。

第四种方法:监听binlog日志(基于canal做数据同步的方案)

对业务代码完全没有侵入性,业务也非常解耦,不需要关心这个ES的更新操作。

缺点就是需要基于binlog监听,需要引入第三方框架。存在一定的延迟。

一致性级别 实现方案 优缺点
强一致性 写入后立即refresh(性能差) 数据最新,吞吐量下降
最终一致性 默认1秒刷新 + 手动_refresh(平衡选择) 性能高,短暂延迟

与Canal集成:主要的过程

  1. MySQL BinlogCanal Server 解析变更。

  2. Canal ClientKafka 发送变更事件。

  3. Logstash/自定义Consumer写入ES

关于Logstash和自定义Consumer

. Logstash 是什么?
  • 定位:开源的数据处理管道工具,属于 Elastic Stack(ELK)的一部分。

  • 核心功能

    • 数据输入(Input):从 Kafka、MySQL、文件等读取数据。

    • 数据过滤(Filter):清洗、转换、丰富数据(如字段重命名、类型转换)。

    • 数据输出(Output):将处理后的数据写入 ES、MySQL、文件等。

  • 特点

    • 配置驱动:通过配置文件定义数据处理流程,无需编码。

    • 插件化 :支持 200+ 官方/社区插件(如 kafkaelasticsearchgrok)。

Canal → ES 场景中的用途

将 Canal 发送到 Kafka 的 MySQL Binlog 数据转换为 ES 所需的格式,并写入 ES。

2. 自定义 Consumer 是什么?
  • 定位:用户自行编写的程序(通常用 Java/Python/Go),用于消费 Kafka 消息并处理。

  • 核心功能

    • 从 Kafka 拉取 Canal 生成的 Binlog 消息。

    • 解析消息并转换为 ES 支持的格式(如 JSON)。

    • 调用 ES 的 API 写入数据。

  • 特点

    • 灵活可控:可自定义业务逻辑(如特殊字段处理、错误重试)。

    • 高性能:通过批量写入、异步请求等优化吞吐量。

整体架构流程:

3. ES支持的数据结构
  • 核心类型

    • Text:用于存储全文文本数据。

    • Keyword:用于存储文本值,通常用于索引结构化内容,如邮件地址、标签或任何需要精确匹配的内容。

    • Nested:类似于 Object 类型,但用于存储数组列表,其中列表中的每个元素都是完全独立且可搜索的。

    • Long, Integer, Short, Byte, Double, Float: 这些是数值类型,用于存储各种形式的数字。

    • Date: 存储日期或日期和时间。

    **Boolean:**存储 true 或 false 值。

    • Binary: 用于存储二进制数据。

    • **Object:**用于嵌套文档,即文档内部可以包含文档。

  • 特殊类型

    • Flattened:避免深层JSON字段爆炸。

    • Join:父子文档(性能较差,慎用)。

text 和 keyword 有啥区别?

text 类型被设计用于全文搜索。这意味着当文本被存储为text 类型 时,Elasticsearch 会对其进行分词, 把文本分解成单独的词或短语,便于搜索引擎进行全文搜索。因为 text 字段经过分词,它不适合用于排序或聚合查询。

text适用于存储需要进行全文搜索的内容,比如新闻文章、产品描述等。

keyword 类型用于精确值匹配 ,不进行分词处理。这意味着存储在 keyword 字段的文本会被当作一个完整不可分割的单元进行处理。因为 keyword 类型字段是作为整体存储,它们非常适合用于聚合(如计数、求和、过滤唯一值等)和排序操作。由于不进行分词,keyword 类型字段不支持全文搜索,但可以进行精确匹配查询。

keyword适用于需要进行精确搜索的场景,比如标签、ID 编号、邮箱地址等。
*

  • ES 不支持 decimal,如何避免丢失精度?

五种方法:

一:使用字符串类型(比较好用)

完全保留数字的精度。简单易于实现,数据迁移时不需特别处理。但是作为字符串存储的数字不能直接用于数值比较或数学运算,需要在应用层处理转换。但也不是什么大毛病。

二:扩大浮点类型的精度

就是直接使用 double 类型,在理论上可能会有精度损失,但实际上 double 类型提供的精度对于许多业务需求已经足够使用。需要接受减小精度损失。

三:使用scaled_float(推荐)

Elasticsearch 的 scaled_float 类型是一种数值数据类型,专门用于存储浮点数。其特点是通过一个缩放因子(scaling factor)将浮点数转换为整数来存储,从而在一定范围内提高存储和计算的效率。他使用一个缩放因子将浮点数转换为整数存储。例如,如果缩放因子是 100,那么值 123.45 会存储为 12345。这样可以避免浮点数存储和计算中的精度问题。

四:使用多个字段

在某些情况下,可以将 decimal 数值拆分为两个字段存储:一个为整数部分,另一个为小数部分。这样做可以在不丢失精度的情况下,将数值分开处理。可以保持数值精确,同时可进行部分数学运算。但是增加了数据处理的复杂性,需要在应用层重建数值。

五:使用自定义脚本

用 Elasticsearch 的脚本功能(如 Painless 脚本)来处理数值计算,确保在处理过程中控制精度。优点:灵活控制数据处理逻辑。缺点:可能影响查询性能,增加系统复杂性。


四、实际应用场景
1. 电商搜索
  • 需求:支持颜色、品牌、价格区间等多维度过滤。

  • 实现

    复制代码
    {
      "query": {
        "bool": {
          "must": [
            { "term": { "brand": "华为" } },
            { "range": { "price": { "gte": 1000, "lte": 2000 } } }
          ],
          "filter": { "term": { "color": "红色" } }
        }
      },
      "aggs": {
        "price_stats": { "stats": { "field": "price" } }
      }
    }
2. 日志告警
  • 需求:实时检测ERROR日志并触发告警。

  • 实现

    • Watcher 插件定时查询:

      复制代码
      {
        "query": { "match": { "level": "ERROR" } },
        "condition": { "compare": { "ctx.hits.total": { "gt": 0 } } },
        "actions": { "email": { "to": "[email protected]" } }
      }
3. 数据同步容灾
  • 双写方案

    • 应用同时写MySQL和ES,通过本地事务表记录同步状态。
  • 补偿机制

    • 定时任务扫描MySQL与ES差异数据,修复不一致。

五、性能调优

集群和硬件优化

负载均衡: 确保查询负载在集群中均衡分配。

硬件资源: 根据需要增加 CPU、内存或改善 I/O 性能(例如使用 SSD)。

配置 JVM: 优化 JVM 设置,如堆大小,以提高性能。

  • 硬件:SSD磁盘、32GB+内存(堆内存不超过31GB)。

  • 索引设计

    选择合适的分词器
    冷热分离:hot-warm架构(热数据用SSD,冷数据用HDD)。

    • 生命周期管理(ILM):自动滚动到新索引。
  • 查询优化

    • 避免wildcard、regexp查询(如*test*)。

    • 使用constant_score过滤不相关文档。

    • 使用过滤器: 对于不需要评分的查询条件,使用 filter 而不是 query,因为 filter 可以被缓存以加快后续相同查询的速度。

    • 合理使用聚合:聚合可以用于高效地进行数据分析,但复杂的聚合也可能非常消耗资源。优化聚合查询,如通过限制桶的数量,避免过度复杂的嵌套聚合。

    • 查询尽可能少的字段: 只返回查询中需要的字段,减少数据传输和处理时间。

    • 避免深度分页: 避免深度分页,对于需要处理大量数据的情况,考虑使用 search_after。


总结

  • 选型:ES适合搜索/分析,MySQL适合事务/精准查询。

  • 核心能力:分词器、倒排索引、分布式计算支撑高效查询。

  • 一致性 :通过refresh、Canal同步等方案平衡实时性与性能。

  • 实践:结合业务场景选择分页策略、数据结构、同步机制。

相关推荐
coding随想1 小时前
深入浅出数据库规范化的三大范式
数据库·oracle
爱笑的眼睛112 小时前
uniapp 云开发全集 云数据库
javascript·数据库·oracle·uni-app
小镇敲码人3 小时前
【深入浅出MySQL】之数据类型介绍
android·数据库·mysql
别来无恙1494 小时前
MySQL JOIN详解:掌握数据关联的核心技能
数据库·mysql
小小不董4 小时前
Oracle OCP认证考试考点详解083系列06
linux·数据库·oracle·dba
Dy大叔5 小时前
ThinkPHP 5 根据账户类型的不同统计数据中使用CASE WHEN THEN SQL语句实例
mysql·php
一 乐5 小时前
宿舍报修|宿舍报修小程序|基于Java微信小程序的宿舍报修系统的设计与实现(源码+数据库+文档)
java·数据库·微信小程序·小程序·论文·毕设·宿舍报修小程序
CodeJourney.7 小时前
基于DeepSeek与HTML的可视化图表创新研究
数据库·人工智能·信息可视化·excel
kngines7 小时前
【PostgreSQL数据分析实战:从数据清洗到可视化全流程】3.3 异常值识别(Z-score法/IQR法/业务规则法)
数据库·postgresql·数据分析·z-score法·iqr法·业务规则法