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二进制数据
ipIP 地址
range区间类型:integer_range, float_range, date_range
geo_point / geo_shape地理坐标和形状
completion自动完成建议
alias为现有字段定义别名
object单个 JSON 对象
nestedJSON 对象数组,可独立查询每个子对象

动态映射(Dynamic Mapping)

ES 可以根据 JSON 数据的格式自动推断字段类型:

JSON 值推断类型
整数long
浮点数float
true/falseboolean
日期格式字符串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"
      }
    }
  }
}

常用映射参数

参数默认值说明
indextrue是否对当前字段创建倒排索引,设为 false 则该字段不可被搜索
analyzerstandard指定分析器
boost1字段权重,影响相关性评分
coercetrue是否允许强制类型转换(如 “1” → 1)
doc_valuestrue是否生成正排索引(用于排序和聚合),text 类型不支持
fielddatafalse在 JVM 堆中创建临时正排索引,仅 text 类型需要聚合时使用
ignore_above256keyword 类型超出该长度的字符串不会被索引
ignore_malformedfalse是否忽略类型转换异常
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必须匹配,不贡献评分,可缓存

注意:当 shouldmust/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 脚本。