写在前面
本文是 Elasticsearch 学习笔记系列的第四篇,介绍进阶查询技巧、分词器、中文分词、搜索调优和索引性能优化。前置知识:聚合分析(第三篇)。
一、深度分页问题
1.1 from/size 的问题
1
2
3
4
5
6
7
8
9
10
11
12
|
from + size 的工作方式:
假设 5 个分片,请求 from=9990, size=10
1. 协调节点向 5 个分片各请求前 10000 条
2. 每个分片返回 10000 条(共 50000 条)
3. 协调节点合并排序 50000 条
4. 取第 9991-10000 条返回
从 = 9990 时,实际处理了 50000 条数据
from 越大,性能越差
ES 默认限制 from + size <= 10000
|
1.2 解决方案
1
2
3
4
5
6
7
8
9
10
11
12
|
方案1:search_after(推荐,实时翻页)
适合:UI 上的"加载更多"/"下一页"
特点:基于排序值定位,性能恒定,不能跳页
方案2:scroll(适合批量导出)
适合:全量数据导出、数据迁移
特点:创建快照,不适合实时查询
方案3:增大 max_result_window(不推荐)
PUT /products/_settings
{ "index.max_result_window": 50000 }
只是提高上限,深分页性能问题没解决
|
1.3 search_after 详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 第1次查询
GET /products/_search
{
"size": 100,
"sort": [
{ "created_at": "desc" },
{ "_id": "asc" }
]
}
// 返回结果中最后一条的 sort 值:["2026-03-15", "42"]
// 第2次查询
GET /products/_search
{
"size": 100,
"sort": [
{ "created_at": "desc" },
{ "_id": "asc" }
],
"search_after": ["2026-03-15", "42"]
}
// 基于上一页最后的排序值继续查
// 注意:sort 字段必须唯一(用 _id 兜底)
|
二、分词器详解
2.1 分词流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
输入文本 → Character Filters → Tokenizer → Token Filters → 倒排索引
Character Filters(字符过滤器):
- 去除 HTML 标签
- 字符替换
- 正则替换
Tokenizer(分词器):
- 按规则拆分为 Token(词元)
- standard:按单词边界切分
- whitespace:按空格切分
Token Filters(词元过滤器):
- 小写转换
- 去停用词(the, a, is)
- 词干提取(running → run)
- 同义词替换
|
2.2 内置分词器
1
2
3
4
5
6
|
standard — 标准分词器(默认),按单词边界切分 + 小写
simple — 按非字母字符切分 + 小写
whitespace — 按空格切分(不做小写)
keyword — 不分词,整体作为一个 Token
pattern — 按正则表达式切分
language — 特定语言分词(english、french 等)
|
2.3 测试分词效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 使用 _analyze API 测试
GET /_analyze
{
"analyzer": "standard",
"text": "华为 Mate 60 Pro 手机"
}
// 返回分词结果:["华为", "mate", "60", "pro", "手", "机"]
// 指定索引的某个字段分析器
GET /products/_analyze
{
"field": "name",
"text": "华为 Mate 60 Pro"
}
// 查看索引字段的分词结果
GET /products/_doc/1/_termvectors?fields=name
|
三、中文分词
3.1 问题:standard 对中文效果差
1
2
3
4
5
|
standard 分词器处理中文:
"华为旗舰手机" → ["华", "为", "旗", "舰", "手", "机"]
问题:每个字作为一个词,无法按词搜索
搜索 "旗舰" 时,"旗" 和 "舰" 分别在不同的文档中也会匹配
|
3.2 IK 分词器
1
2
3
4
5
|
安装(ES 8.15):
./bin/elasticsearch-plugin install \
https://github.com/infinilabs/analysis-ik/releases/download/v8.15.0/elasticsearch-analysis-ik-8.15.0.zip
安装后重启 ES
|
3.3 IK 两种模式
1
2
3
4
5
|
ik_max_word — 最细粒度切分(索引时用)
"华为旗舰手机" → ["华为", "旗舰", "手机"]
ik_smart — 最粗粒度切分(搜索时用)
"华为旗舰手机" → ["华为", "旗舰", "手机"]
|
3.4 使用 IK 分词器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 创建索引时指定
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"description": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
}
}
}
}
// analyzer — 索引时使用的分词器(细粒度,多建倒排索引)
// search_analyzer — 搜索时使用的分词器(粗粒度,精确匹配)
|
3.5 自定义词库
1
2
3
4
5
6
7
8
9
10
|
// 扩展词典(添加新词)
// config/IKAnalyzer.cfg.xml
<properties>
<entry key="ext_dict">custom.dic</entry>
</properties>
// custom.dic(每行一个词)
Mate60
鸿蒙
麒麟芯片
|
3.6 IK 分词测试
1
2
3
4
5
6
7
8
9
10
11
12
13
|
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "华为旗舰手机搭载麒麟芯片"
}
// ["华为", "旗舰", "手机", "搭载", "麒麟", "芯片"]
GET /_analyze
{
"analyzer": "ik_smart",
"text": "华为旗舰手机搭载麒麟芯片"
}
// ["华为", "旗舰", "手机", "搭载", "麒麟芯片"]
|
四、相关性评分调优
4.1 评分机制(BM25)
1
2
3
4
5
6
7
8
9
10
|
ES 默认使用 BM25 算法计算相关性评分
影响因素:
1. 词频(TF) — 搜索词在文档中出现的次数越多,分数越高
2. 文档频率(IDF) — 搜索词在整个索引中越罕见,分数越高
3. 字段长度 — 字段越短,匹配到的词越重要
搜索 "华为":
文档A:"华为 Mate 60 Pro"(短字段,"华为" 占比高)→ 高分
文档B:"这是一款华为生产的旗舰手机,搭载麒麟芯片..."(长字段)→ 低分
|
4.2 function_score(自定义评分)
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
|
// 搜索 "手机",价格低的排名靠前
GET /products/_search
{
"query": {
"function_score": {
"query": {
"match": { "name": "手机" }
},
"functions": [
{
"field_value_factor": {
"field": "rating",
"factor": 2,
"modifier": "sqrt"
}
}
],
"boost_mode": "multiply"
}
}
}
// modifier 选项:
// none — 不处理
// log — 取对数(压缩范围)
// sqrt — 平方根
// square — 平方
// reciprocal — 1/x
|
4.3 boosting(提升/降低权重)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// 搜索 "手机",华为品牌加权,小品牌降权
GET /products/_search
{
"query": {
"boosting": {
"positive": {
"bool": {
"must": [
{ "match": { "name": "手机" } }
],
"should": [
{ "term": { "brand": { "value": "华为", "boost": 2 } } }
]
}
},
"negative": {
"term": { "brand": "山寨" }
},
"negative_boost": 0.5
}
}
}
// positive 匹配的正常评分
// negative 匹配的评分 × 0.5
|
4.4 constant_score
1
2
3
4
5
6
7
8
9
10
11
12
|
// 不计算相关性评分,所有匹配的文档评分都一样(适合纯过滤)
GET /products/_search
{
"query": {
"constant_score": {
"filter": {
"term": { "category": "手机" }
},
"boost": 1.2
}
}
}
|
五、索引性能优化
5.1 Bulk 批量写入
1
2
3
4
5
6
7
8
9
10
11
|
最佳实践:
- 每批 1000-5000 条文档
- 批量大小 5-15MB
- 多线程并发写入
// 批量写入示例
POST /_bulk
{"index":{"_index":"products","_id":"1"}}
{"name":"华为 Mate 60","price":6999}
{"index":{"_index":"products","_id":"2"}}
{"name":"小米 14","price":3999}
|
5.2 Refresh 和 Flush
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
写入流程:
1. 写入 Index Buffer
2. Refresh(默认 1 秒)→ Buffer 写入新的 Segment → 可搜索
3. Flush → Segment 持久化到磁盘 + 清除 Translog
调优:
- 提高刷新间隔(批量写入时)
PUT /products/_settings
{ "index.refresh_interval": "30s" }
- 批量写入完成后恢复
PUT /products/_settings
{ "index.refresh_interval": "1s" }
- 批量导入时可以设为 -1(关闭自动刷新)
导入完成后手动刷新:POST /products/_refresh
|
5.3 Translog 配置
1
2
3
4
5
6
7
|
PUT /products/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
}
// async:异步刷盘,性能更好但有丢失风险
// request:每次请求都刷盘(默认,安全但慢)
|
5.4 索引设计优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
1. 合理设置分片数
- 小索引(< 1GB):1 个分片
- 中等索引(1-50GB):3-5 个分片
- 大索引(> 50GB):按 30-50GB/分片计算
2. 避免过多字段
- 字段过多 → Mapping 膨胀 → 性能下降
- 不需要搜索的字段设置 "index": false
- 不需要聚合的字段设置 "doc_values": false
3. 使用 _source 过滤
- 不需要返回的大字段可以用 store + _source excludes
4. 索引按时间滚动
- logs-2026.05.01, logs-2026.05.02
- 方便删除过期数据(直接删整个索引)
|
5.5 查询优化
1
2
3
4
5
6
|
1. 用 filter 代替 query(不评分,会缓存)
2. 避免深分页(用 search_after)
3. 只返回需要的字段(_source 过滤)
4. 避免用 wildcard/regexp 前缀通配符(如 *华为)
5. 控制返回的桶数量(terms 的 size)
6. 避免在高基数字段上做聚合
|
六、Kibana 使用技巧
1
2
3
4
5
|
Kibana Dev Tools 快捷键:
Ctrl + Enter — 执行当前请求
Ctrl + / — 注释/取消注释
Ctrl + Space — 自动补全
Ctrl + ↑/↓ — 跳转到上/下一个请求
|
6.2 Discover(数据浏览)
1
2
3
4
5
|
1. 选择索引模式(如 logs-*)
2. 设置时间范围
3. 用 KQL 或 Lucene 语法过滤
4. 添加字段列显示
5. 保存搜索条件
|
6.3 Dashboard(仪表盘)
1
2
3
4
5
6
7
8
|
1. 创建可视化(Visualization)
- 柱状图、饼图、折线图、数据表格
- 基于聚合结果展示
2. 组合到 Dashboard
- 多个可视化组件排布
- 全局时间过滤
- 共享给团队
|
七、小结
本文学习了进阶查询与优化:
- 深度分页问题(search_after、scroll)
- 分词器详解(Analyzer、Tokenizer、Token Filter)
- 中文分词(IK 分词器、自定义词库)
- 相关性评分调优(BM25、function_score、boosting)
- 索引性能优化(Bulk、Refresh、Flush、分片设计)
- 查询优化技巧
- Kibana 使用技巧
下一篇将学习集群运维:节点角色、分片分配、ILM 和监控排查。