Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

中间件 / Middleware

1. Web 中间件全景 / The Web Middleware Landscape

2. 中间件——流量洪峰下的系统保护 / Middleware for Protecting Systems Under Traffic Spikes

cache layer

1. 缓存架构全景 / Cache Architecture Overview

2. Redis 深入——为什么你的缓存总是出问题 / Redis Internals and Cache Failure Modes

3. Redis 数据结构场景应用——什么时候用什么 / Choosing Redis Data Structures for Real Applications

4. 缓存策略——库存变了,缓存怎么处理 / Cache Strategies and Inventory Consistency

5. Redis 缓存三剑客——穿透/击穿/雪崩 + 一致性策略 / Redis Cache Penetration, Breakdown, Avalanche, and Consistency

6. Redis 分布式锁——从 SETNX 到 Redisson / Redis Distributed Locks from SETNX to Redisson

7. Redis Cluster 与 Sentinel 高可用架构 / Redis Cluster and Sentinel High-Availability Architecture

8. Redis 高级特性——Stream / PubSub / Module / LLM 应用

9. Redis 架构深度分析——为什么 Redis 能这么快 / Redis Architecture and the Sources of Its Performance

10. Redis 全景——为什么你的系统需要一个缓存层 / The Redis Landscape and Why Systems Need a Cache Layer

message queue

1. 消息队列全景——为什么你的系统需要一个中间人 / Message Queue Overview and Why Your System Needs a Middleman

2. Kafka 核心——为什么你的消息总是"丢"了 / Kafka Fundamentals and Message Delivery Semantics

3. 消息队列高级——死信队列、延迟消息、消息积压 / Advanced Messaging with Dead Letters, Delays, and Backlogs

4. Kafka Streams 与 Connect —— 让数据自己流动起来 / Kafka Streams and Connect for Streaming Data Pipelines

5. RabbitMQ 深度解析 —— 灵活路由与消息可靠性 / RabbitMQ Deep Dive into Flexible Routing and Reliability

6. 消息队列对比——为什么最终选了 Kafka / Comparing Message Queues and Choosing Kafka

search engine

1. 搜索引擎知识体系 / Search Engine Knowledge System

2. Elasticsearch——为什么 Like 查询总是那么慢 / Elasticsearch for Full-Text Search at Scale

3. Elasticsearch 查询 DSL 深入——为什么你的搜索总是不准 / Elasticsearch Query DSL Deep Dive

4. Elasticsearch 集群规划与运维——为什么你的集群总是"黄" / Elasticsearch Cluster Planning and Operations

5. Meilisearch 与轻量搜索替代方案——当 ES 太重时 / Meilisearch and Lightweight Search Alternatives

infrastructure

1. 基础设施组件全景 / Infrastructure Components Landscape

2. Nginx 与反向代理 / Nginx and Reverse Proxy

3. 服务发现 / Service Discovery

4. 配置中心 / Configuration Center

本页目录

Elasticsearch——为什么 Like 查询总是那么慢 / Elasticsearch for Full-Text Search at Scale ​

📅 创建时间:2026-05-08 🏷️ 标签:#Elasticsearch #倒排索引 #ES分词 #全文搜索 #搜索建议 📚 前置知识:[[00-backend-overview]] [[09-mysql-optimization]](索引原理) 📚 相关知识:[[/03-web/06-databases-and-data-access/09-olap-databases]](列式存储) [[11-architecture-patterns]](CQRS)


场景:搜索"小米手机"却找不到"小米 13" ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  用户在商品搜索框输入:"小米手机"                        │
│                                                             │
│  SQL 查询:                                               │
│  SELECT * FROM products                                   │
│  WHERE name LIKE '%小米手机%'                            │
│                                                             │
│  结果:找到 0 条(因为商品名是"小米 13 手机")          │
│                                                             │
│  用户困惑:我明明搜的就是小米,为什么搜不到?             │
│                                                             │
│  产品经理:搜索体验太差了,转化率掉了 30%。             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

MySQL 的 LIKE 是最左匹配,ES 的倒排索引是语义匹配。这一章,我们理解 ES 为什么能让搜索变快变准。


第1节:倒排索引——从"找文档"到"找词" ​

问题:MySQL LIKE 为什么慢 ​

┌─────────────────────────────────────────────────────────────┐
│              MySQL LIKE 的工作原理                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  索引:B+ 树,按字段值排序                             │
│                                                             │
│  name = '小米手机'  → 走索引 ✅                         │
│  name LIKE '小米%'  → 走索引(前缀匹配)✅             │
│  name LIKE '%小米%' → 全表扫描!❌                     │
│                                                             │
│  原因:'%' 在前面,无法利用 B+ 树的有序性               │
│                                                             │
│  100 万条数据:                                          │
│  全表扫描 → 100 万次磁盘 IO → 5-10 秒                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

倒排索引的核心思想 ​

┌─────────────────────────────────────────────────────────────┐
│                 倒排索引 vs 正排索引                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  正排索引(MySQL):文档 → 包含哪些词                    │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  文档 1: 小米 13 手机,高性价比                  │  │
│  │  文档 2: 华为 Mate60,拍照神器                   │  │
│  │  文档 3: iPhone 15,体验流畅                    │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  倒排索引(ES):词 → 在哪些文档中                       │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  小米 → [文档 1]                                  │  │
│  │  13   → [文档 1]                                  │  │
│  │  手机 → [文档 1]                                  │  │
│  │  华为 → [文档 2]                                  │  │
│  │  iPhone → [文档 3]                                │  │
│  │  拍照 → [文档 2]                                  │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  搜索"小米":直接查"小米"这个词,O(1) 找到文档列表   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

倒排索引的详细结构 ​

┌─────────────────────────────────────────────────────────────┐
│                 倒排索引详细结构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  词(Term)  →  倒排列表(Posting List)                 │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  词项词典(Term Dictionary)                       │  │
│  │  小米 → 偏移量 100                               │  │
│  │  手机 → 偏移量 200                               │  │
│  │  华为 → 偏移量 300                               │  │
│  └─────────────────────────────────────────────────────┘  │
│                       ↓                                    │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  倒排文件(Postings File)                        │  │
│  │  [偏移量 100] → docID=1, freq=1, positions=[0]  │  │
│  │  [偏移量 200] → docID=1, freq=1, positions=[2]  │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  docID:文档 ID                                           │
│  freq:词在文档中出现次数                                 │
│  positions:词在文档中的位置(用于短语搜索)             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

第2节:分词器——为什么"苹果"能搜到"apple" ​

问题:中文怎么分词 ​

┌─────────────────────────────────────────────────────────────┐
│                 分词的歧义                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  句子:"中华人民共和国成立"                              │
│                                                             │
│  最小切分:                                              │
│  中华 / 人民 / 共和国 / 成立                             │
│                                                             │
│  最大切分:                                              │
│  中华人民共和国 / 成立                                   │
│                                                             │
│  搜索引擎需要:                                          │
│  → 粒度适中(太细语义丢失,太粗匹配不准)              │
│  → 歧义消解("行"是多音字)                            │
│  → 未知词识别(新词、热词)                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

ES 分词器对比 ​

┌─────────────────────────────────────────────────────────────┐
│                 分词器对比                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  standard(默认):                                        │
│  → 英文:按空格 + 标点切分,lowercase                   │
│  → 中文:单字切分                                         │
│  → "小米手机" → "小", "米", "手", "机"               │
│  → 缺点:失去语义,相邻单字无法关联                     │
│                                                             │
│  ik_max_word(最细粒度):                               │
│  → 中文:最大词数切分                                     │
│  → "小米手机" → "小米", "手机"                         │
│  → 优点:召回率高                                        │
│  → 缺点:precision 可能下降                              │
│                                                             │
│  ik_smart(最粗粒度):                                  │
│  → 中文:最大语义切分                                    │
│  → "中华人民共和国" → "中华人民共和国"                 │
│  → 优点:精确率高                                        │
│                                                             │
│  推荐策略:                                              │
│  索引时:ik_max_word(最大程度索引)                    │
│  搜索时:ik_smart(精准匹配)                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

自定义分词器配置 ​

json
PUT /products
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "ik_max_word",
          "filter": ["lowercase", "synonym_filter"]
        }
      },
      "filter": {
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "手机,移动电话,智能手机",
            "电脑,计算机,笔记本",
            "小米,MI,红米"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      },
      "description": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "price": {
        "type": "float"
      },
      "category": {
        "type": "keyword"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

第3节:ES 搜索实战 ​

基础查询 ​

python
from elasticsearch import Elasticsearch

es = Elasticsearch(["http://localhost:9200"])

# 全文搜索
result = es.search(index="products", body={
    "query": {
        "match": {
            "name": "小米手机"  # 会分词搜索
        }
    }
})

# 短语搜索(保持词的相邻关系)
result = es.search(index="products", body={
    "query": {
        "match_phrase": {
            "name": {
                "query": "小米手机",
                "slop": 1  # 允许 1 个词的距离
            }
        }
    }
})

# 多字段搜索 + 权重
result = es.search(index="products", body={
    "query": {
        "multi_match": {
            "query": "小米手机",
            "fields": ["name^3", "description"],  # name 权重更高
            "type": "best_fields"
        }
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

过滤 + 聚合 + 高亮 ​

python
# 复杂搜索:过滤 + 排序 + 高亮
result = es.search(index="products", body={
    "query": {
        "bool": {
            "must": {
                "match": {"name": "手机"}
            },
            "filter": [
                {"range": {"price": {"gte": 2000, "lte": 5000}}},
                {"term": {"status": "active"}},
                {"terms": {"category": ["手机", "数码"]}}
            ]
        }
    },
    "sort": [
        {"sales": "desc"},    # 销量优先
        {"price": "asc"}      # 价格其次
    ],
    "from": 0,
    "size": 20,
    "highlight": {
        "fields": {
            "name": {
                "pre_tags": ["<em>"],
                "post_tags": ["</em>"]
            }
        }
    }
})

# 聚合统计
result = es.search(index="products", body={
    "size": 0,  # 不返回文档,只返回聚合结果
    "query": {"match": {"name": "手机"}},
    "aggs": {
        "avg_price": {"avg": {"field": "price"}},
        "price_ranges": {
            "range": {
                "field": "price",
                "ranges": [
                    {"to": 1000},
                    {"from": 1000, "to": 3000},
                    {"from": 3000}
                ]
            }
        },
        "top_brands": {
            "terms": {"field": "brand", "size": 5}
        }
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

第4节:搜索建议——为什么输入框能自动补全 ​

场景:输入"小"就出现"小米" ​

┌─────────────────────────────────────────────────────────────┐
│                 搜索建议的三种实现                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方案 1:Prefix Query(最简单)                          │
│  → 用前缀匹配实现即时补全                                │
│  → 搜索速度快,但无法处理同音字/拼写错误                  │
│                                                             │
│  方案 2:Completion Suggester(推荐)                     │
│  → 使用 FST(有限状态转换器)                           │
│  → 专为补全设计,性能极高                                │
│  → 支持权重排序                                          │
│                                                             │
│  方案 3:Phrase Suggester(智能纠错)                   │
│  → 处理拼写错误、同音字                                  │
│  → 搜索"iphon"能返回"iPhone 15"                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Completion Suggester 实现 ​

json
PUT /products/_mapping
{
  "properties": {
    "suggest": {
      "type": "completion",
      "analyzer": "simple",
      "preserve_separators": true,
      "preserve_position_increments": true,
      "max_input_length": 50
    },
    "name": {"type": "text"},
    "price": {"type": "float"}
  }
}

PUT /products/_doc/1
{
  "name": "小米 13 手机",
  "price": 3999,
  "suggest": {
    "input": ["小米13", "小米手机", "MI 13", "XiaoMi 13"],
    "weight": 100
  }
}

POST /products/_search
{
  "suggest": {
    "product-suggest": {
      "prefix": "小",
      "completion": {
        "field": "suggest",
        "size": 5,
        "skip_duplicates": true
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

第5节:ES 在搜索推荐中的应用 ​

多维度排序实战 ​

python
# 搜索 + 多维度综合排序
result = es.search(index="products", body={
    "query": {
        "function_score": {
            "query": {
                "bool": {
                    "must": {"match": {"name": "手机"}},
                    "filter": {"range": {"price": {"lte": 5000}}}
                }
            },
            "functions": [
                {
                    "field_value_factor": {
                        "field": "sales",
                        "factor": 1.2,
                        "modifier": "log1p",
                        "missing": 1
                    }
                },
                {
                    "gauss": {
                        "price": {
                            "origin": 3000,
                            "scale": 1000,
                            "decay": 0.5
                        }
                    }
                },
                {
                    "filter": {"term": {"is_hot": true}},
                    "weight": 2
                }
            ],
            "score_mode": "sum",
            "boost_mode": "multiply"
        }
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

升华:MySQL vs ES 的选择 ​

┌─────────────────────────────────────────────────────────────┐
│              MySQL vs ES 选择指南                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  选 MySQL(全文搜索):                                  │
│  ✅ 数据量小(<100 万)                                 │
│  ✅ 查询简单(关键词匹配)                              │
│  ✅ 一致性要求高(ES 是最终一致)                       │
│  ✅ 已有 MySQL,不想引入新组件                          │
│                                                             │
│  选 ES(全文搜索):                                     │
│  ✅ 搜索体验要求高(纠错/同义词/高亮)                  │
│  ✅ 数据量大(>100 万)                                 │
│  ✅ 需要复杂聚合(facet search)                        │
│  ✅ 搜索和 OLAP 混合场景                                │
│                                                             │
│  最佳实践:MySQL + ES 双写                               │
│  → MySQL 作为主库(事务、一致性)                      │
│  → ES 作为搜索库(全文搜索、聚合)                      │
│  → 数据同步:Canal / Debezium 监听 binlog              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ ES 的各种 Query DSL 语法
✅ 分词器的具体配置参数
✅ FST 的数据结构细节

必须理解:
🔴 倒排索引和正排索引的核心区别
🔴 为什么 ES 搜索比 MySQL LIKE 快
🔴 索引时分词和搜索时分词的区别
🔴 ik_max_word vs ik_smart 的适用场景
🔴 Completion Suggester 的应用场景
1
2
3
4
5
6
7
8
9
10
11

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇1. 搜索引擎知识体系 / Search Engine Knowledge System
下一篇3. Elasticsearch 查询 DSL 深入——为什么你的搜索总是不准 / Elasticsearch Query DSL Deep Dive

持续记录,持续成长

Copyright © Tidenflow