2026年7月12日 · 6 分钟阅读
Elasticsearch 搜索与查询实战:Mapping、分析器与复杂查询
系统梳理 ES 的数据类型与映射、全文搜索与精确查询的异同、自定义分析器的构建方式,以及组合查询与聚合分析的实践用法。
用好 ES,核心是理解三件事:数据怎么存(Mapping)、搜索怎么查(Query)、文本怎么切(Analyzer)。这篇文章把它们串起来讲,中间穿插大量可以直接在 Kibana 运行的示例。
Mapping:数据怎么存
常见数据类型
| 类型 | 说明 |
|---|---|
text | 全文搜索字段,内容会被分词器分析,生成倒排索引 |
keyword | 结构化数据,作为整体存入倒排索引,不进行分词。适合精确匹配、过滤、排序、聚合 |
integer / long / float / double | 数值类型,range 查询有优化 |
date / date_nanos | 日期类型 |
boolean | 布尔值 |
binary | 二进制数据 |
ip | IP 地址 |
range | 区间类型:integer_range, float_range, date_range 等 |
geo_point / geo_shape | 地理坐标和形状 |
completion | 自动完成建议 |
alias | 为现有字段定义别名 |
object | 单个 JSON 对象 |
nested | JSON 对象数组,可独立查询每个子对象 |
动态映射(Dynamic Mapping)
ES 可以根据 JSON 数据的格式自动推断字段类型:
| JSON 值 | 推断类型 |
|---|---|
| 整数 | long |
| 浮点数 | float |
| true/false | boolean |
| 日期格式字符串 | date |
| 数组 | 取决于数组中第一个有效值 |
| 对象 | object |
| 其他字符串 | text + keyword(双字段) |
显式映射(Explicit Mapping)
PUT /product
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "integer"
},
"tags": {
"type": "keyword"
},
"date": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
常用映射参数
| 参数 | 默认值 | 说明 |
|---|---|---|
index | true | 是否对当前字段创建倒排索引,设为 false 则该字段不可被搜索 |
analyzer | standard | 指定分析器 |
boost | 1 | 字段权重,影响相关性评分 |
coerce | true | 是否允许强制类型转换(如 “1” → 1) |
doc_values | true | 是否生成正排索引(用于排序和聚合),text 类型不支持 |
fielddata | false | 在 JVM 堆中创建临时正排索引,仅 text 类型需要聚合时使用 |
ignore_above | 256 | keyword 类型超出该长度的字符串不会被索引 |
ignore_malformed | false | 是否忽略类型转换异常 |
copy_to | — | 将多个字段的值合并到一个虚拟字段中 |
format | — | 日期格式 |
_source 元数据
_source 字段存储了原始的 JSON 文档内容。可以在查询时控制返回哪些字段:
// 不返回 _source
GET /product/_search
{
"_source": false,
"query": { "match_all": {} }
}
// 只返回指定字段
GET /product/_search
{
"_source": ["name", "price"],
"query": { "match_all": {} }
}
// 包含/排除模式
GET /product/_search
{
"_source": {
"includes": ["owner.*", "name"],
"excludes": ["desc", "price"]
},
"query": { "match_all": {} }
}
分析器(Analyzer):文本怎么切
分析器是 ES 全文搜索的”分词引擎”,决定了搜索时哪些词会被匹配。
一个分析器由三部分构成:
分析器 = char_filter(字符过滤) → tokenizer(分词器) → filter(词项过滤)
char_filter(字符过滤器)
在分词之前对原始文本做预处理:
HTML 转义清洗:去除 HTML 标签
PUT my_index
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "html_strip",
"escaped_tags": ["a"]
}
},
"analyzer": {
"my_analyzer": {
"char_filter": ["my_char_filter"],
"tokenizer": "keyword"
}
}
}
}
}
映射替换:将指定字符替换为其他字符
"my_char_filter": {
"type": "mapping",
"mappings": ["垃=>*", "圾=>*"]
}
正则替换:用正则匹配并替换
"my_char_filter": {
"type": "pattern_replace",
"pattern": "(\\d{3})\\d{4}(\\d{4})",
"replacement": "$1****$2"
}
tokenizer(分词器)
Lucene 内置了多种分词器:
| 分词器 | 行为 |
|---|---|
standard | 按 Unicode 文本边界切分,去掉标点 |
keyword | 不做任何切分,整个文本作为一个词项 |
whitespace | 按空白字符切分 |
pattern | 按正则匹配到的分隔符切分 |
ik_smart / ik_max_word | 中文分词(需要 IK 插件) |
filter(词项过滤器)
分词之后对词项做进一步处理:
同义词:
"my_filter": {
"type": "synonym",
"synonyms": ["赵,钱,孙,李=>吴"]
}
停用词:
// 方式一:在分析器上直接配置
"my_analyzer": {
"type": "standard",
"stopwords": ["me", "you"]
}
// 方式二:通过 filter 配置
"my_filter": {
"type": "stop",
"stopwords": ["me", "you"]
}
自定义分析器
把上面三部分组合起来,创建一个完整自定义分析器:
PUT my_index
{
"settings": {
"analysis": {
"char_filter": {
"my_char_filter": {
"type": "mapping",
"mappings": ["&=>and", "|=>or"]
}
},
"filter": {
"my_filter": {
"type": "stop",
"stopwords": ["is", "in", "a", "at"]
}
},
"tokenizer": {
"my_tokenizer": {
"type": "pattern",
"pattern": "[ ,.!]"
}
},
"analyzer": {
"my_analyzer": {
"char_filter": ["my_char_filter"],
"filter": ["my_filter"],
"tokenizer": "my_tokenizer",
"type": "custom"
}
}
}
}
}
测试分析器的效果:
GET my_index/_analyze
{
"analyzer": "my_analyzer",
"text": "Hello & World! Good is great."
}
查询:数据怎么搜
URI 查询
最简单的方式,直接在 URL 上写参数:
# 精确匹配
GET /product/_search?q=name:xiaomi
# 分页+排序
GET /product/_search?from=0&size=2&sort=price:asc
# 全文搜索(在所有索引字段中检索)
GET /product/_search?q=2023-06-01
Request Body 查询
match 查询(全文搜索)
对搜索词进行分词,任一匹配即可:
GET product/_search
{
"query": {
"match": {
"name": "xiaomi nfc phone"
}
}
}
match_phrase 查询(短语匹配)
分词结果必须在被检索字段的分词中都包含,顺序必须相同,且默认连续:
GET product/_search
{
"query": {
"match_phrase": {
"name": "xiaomi nfc"
}
}
}
通过 slop 参数允许词项之间有间隔:
GET product/_search
{
"query": {
"match_phrase": {
"name": {
"query": "xiaomi phone1",
"slop": 1
}
}
}
}
multi_match 查询(多字段搜索)
同时对多个字段进行 match 查询,相当于求笛卡尔积的拼配:
GET product/_search
{
"query": {
"multi_match": {
"query": "phone huangmenji",
"fields": ["name", "desc"]
}
}
}
term 查询(精确匹配)
term 不对搜索词分词,搜索词必须能与倒排索引中的词项完全匹配:
GET product/_search
{
"query": {
"term": {
"tags": "88vip"
}
}
}
区别总结:term 是查询方式(不分词),keyword 是字段类型(索引时不分词)。两者概念不同但常配合使用。
terms 查询(多值精确匹配)
类似 SQL 的 IN:
GET product/_search
{
"query": {
"terms": {
"tags": ["88vip", "gongjiaoka"]
}
}
}
range 查询(范围查询)
GET product/_search
{
"query": {
"range": {
"price": {
"gte": 399,
"lte": 4999
}
}
}
}
日期范围支持 now 表达式和时区:
GET product/_search
{
"query": {
"range": {
"date": {
"gte": "now-1d/d",
"lte": "now/d",
"time_zone": "+08:00"
}
}
}
}
filter(过滤)
和 query 功能一样,但不计算相关性评分,性能更好。适合做数据预处理:
GET product/_search
{
"query": {
"constant_score": {
"filter": {
"term": { "name": "phone" }
},
"boost": 1.2
}
}
}
bool 查询(组合查询)
ES 最强大的查询方式,支持逻辑组合:
GET product/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "xiaomi phone" } },
{ "match_phrase": { "desc": "shouji zhong" } }
],
"filter": [
{ "range": { "price": { "lte": 1000 } } }
]
}
}
}
布尔子句的语义:
| 子句 | 含义 |
|---|---|
must | 必须匹配,贡献评分 |
must_not | 必须不匹配,不贡献评分 |
should | 选择性匹配,类似 SQL 的 OR |
filter | 必须匹配,不贡献评分,可缓存 |
注意:当
should和must/filter搭配时,should默认”躺平”(可以一个都不满足)。要强制至少匹配 N 个条件,需配置minimum_should_match。
基础知识速查
索引 CRUD
// 创建索引
PUT /product?pretty
// 查询索引列表
GET _cat/indices?v
// 查询索引 mapping
GET /product/_mapping
// 删除索引
DELETE /product
// 插入/更新文档
PUT /product/_doc/1
{ "name": "xiaomi", "price": 2999 }
// 删除文档
DELETE /product/_doc/1
// 局部更新
POST /product/_doc/1/_update
{ "doc": { "price": 1999 } }
// 批量操作
POST product/_bulk
{ "index": { "_id": "1" } }
{ "name": "zhangsan1" }
{ "index": { "_id": "2" } }
{ "name": "zhangsan2" }
// 批量查询
GET product/_mget
{ "ids": [1, 2, 3] }
总结
- Mapping 决定了数据怎么存——text 用于全文搜索,keyword 用于精确匹配和聚合,数值类型对 range 查询有优化。
- Analyzer 决定了文本怎么切——三分组合(char_filter → tokenizer → filter)提供了极大的定制空间。
- Query 决定了数据怎么搜——match 做分词搜索,term 做精确匹配,bool 做逻辑组合,filter 做高性能过滤。
把这三点想清楚,ES 的基本使用就不会出大问题。下一篇文章会继续深入聚合分析与 Painless 脚本。