查询详解
ES 的查询 DSL(Domain Specific Language)是其最核心的能力。本文涵盖完整查询分类、相关度评分原理和最佳实践。
查询分类全景
| 大类 | 查询类型 | 说明 | 参与评分 |
|---|---|---|---|
| 查询所有 | match_all |
匹配所有文档 | ❌ |
| 全文检索 | match |
分词后 OR 匹配 | ✅ |
match_phrase |
短语精确匹配,保持顺序 | ✅ | |
match_phrase_prefix |
短语前缀匹配(搜索补全) | ✅ | |
multi_match |
多字段搜索 | ✅ | |
query_string / simple_query_string |
类 Lucene 语法字符串搜索 | ✅ | |
| 精确查询 | term |
词条精确匹配(不分词) | ✅ |
terms |
批量词条匹配 | ✅ | |
range |
范围查询 | ✅ | |
exists |
字段非空 | ❌ | |
prefix |
前缀匹配 | ✅ | |
wildcard |
通配符匹配(* 和 ?) |
✅ | |
regexp |
正则匹配 | ✅ | |
fuzzy |
模糊/纠错匹配 | ✅ | |
ids |
按 _id 批量查询 | ❌ | |
| 地理查询 | geo_distance |
圆形范围 | ✅ |
geo_bounding_box |
矩形范围 | ✅ | |
geo_shape |
复杂地理形状 | ✅ | |
| 复合查询 | bool |
多条件组合 | 子句决定 |
function_score |
自定义评分 | ✅ | |
constant_score |
固定评分(包装 filter) | ❌ | |
boosting |
降权查询 | ✅ | |
dis_max |
取多查询中最佳匹配 | ✅ | |
| 专用查询 | nested |
嵌套对象查询 | ✅ |
has_child / has_parent |
父子关系查询 | ✅ | |
script |
脚本过滤 | ✅ | |
knn |
向量近似搜索 | ✅ |
基本语法
GET /{index}/_search
{
"query": {
"查询类型": {
"字段名": "查询条件"
}
},
"from": 0,
"size": 10,
"_source": ["field1", "field2"],
"sort": [{ "field": "asc" }]
}响应关键字段
| 字段 | 说明 |
|---|---|
took |
ES 执行查询耗时(毫秒) |
timed_out |
搜索是否超时 |
_shards.total / successful / failed |
分片统计 |
hits.total.value |
匹配文档总数(relation: "eq" 为精确值,"gte" 为估算) |
hits.max_score |
文档中最高的相关性得分 |
hits.hits._score |
单个文档得分(filter/match_all 上下文为 null) |
hits.hits._source |
文档原始 JSON |
🔬 深入原理:ES 7.0+ 的
hits.total默认只精确计数到 10000(track_total_hits: 10000)。超过此阈值返回"relation": "gte"。需要精确计数时设置"track_total_hits": true,但这会消耗额外资源。
全文检索
全文检索对搜索条件先分词再匹配,适用于 text 类型字段。
match — 单字段搜索
GET /bank/_search
{
"query": {
"match": {
"address": "mill lane"
}
}
}🔬 深入原理:搜索
"mill lane"→ 分词为["mill", "lane"]→ 默认 OR 关系 → 匹配包含 “mill” 或 “lane” 的文档。
match 的 operator 参数
# AND 关系:同时包含 mill 和 lane
GET /bank/_search
{
"query": {
"match": {
"address": {
"query": "mill lane",
"operator": "and"
}
}
}
}minimum_should_match — 至少匹配 N 个词
# 至少匹配 75% 的词
GET /bank/_search
{
"query": {
"match": {
"description": {
"query": "快速 可靠 分布式 搜索引擎",
"minimum_should_match": "75%"
}
}
}
}match_phrase — 短语匹配
要求词语同时出现且顺序一致。
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "mill lane"
}
}
}| 参数 | 说明 | 默认值 |
|---|---|---|
slop |
允许词语间的间隔(slop=1 允许中间隔一个词) | 0 |
match_phrase_prefix — 前缀短语匹配
适合"边输入边搜索"场景:
GET /_search
{
"query": {
"match_phrase_prefix": {
"title": "elasticsea"
}
}
}⚡ 性能提示:前缀匹配可能产生大量 term 枚举。可通过
max_expansions(默认 50)限制。
multi_match — 多字段搜索
GET /bank/_search
{
"query": {
"multi_match": {
"query": "mill lane",
"fields": ["address", "city", "state"]
}
}
}匹配模式(type 参数):
| type | 说明 |
|---|---|
best_fields |
取最佳匹配字段的得分(默认) |
most_fields |
多字段得分相加 |
cross_fields |
跨字段匹配(如 “张三” 匹配 first_name + last_name) |
phrase |
在每个字段上执行 match_phrase |
phrase_prefix |
在每个字段上执行 match_phrase_prefix |
# cross_fields 典型场景:人名跨字段搜索
GET /_search
{
"query": {
"multi_match": {
"query": "张三",
"fields": ["first_name", "last_name"],
"type": "cross_fields",
"operator": "and"
}
}
}精确查询
精确查询不对搜索条件分词,适用于 keyword、数值、日期、布尔等类型。
term — 词条精确匹配
GET /bank/_search
{
"query": {
"term": {
"state": { "value": "ND" }
}
}
}🚨 陷阱:
term对text字段查询可能查不到——写入时 text 已被分词为多个小写 term,而term不做分词。对 text 字段用match,对 keyword 字段用term。
terms — 批量词条匹配
GET /bank/_search
{
"query": {
"terms": {
"state": ["ND", "CA", "NY"]
}
}
}range — 范围查询
GET /bank/_search
{
"query": {
"range": {
"age": {
"gte": 18,
"lte": 65
}
}
}
}| 参数 | 含义 |
|---|---|
gte |
≥(大于等于) |
gt |
>(大于) |
lte |
≤(小于等于) |
lt |
<(小于) |
format |
日期格式(日期字段专用) |
time_zone |
时区(日期字段专用) |
prefix / wildcard / regexp
# 前缀匹配
GET /_search
{ "query": { "prefix": { "title.keyword": "Elastic" } } }
# 通配符匹配
GET /_search
{ "query": { "wildcard": { "title.keyword": "Elasti*" } } }
# 正则匹配
GET /_search
{ "query": { "regexp": { "title.keyword": "[Ee]lastic.*" } } }⚡ 性能提示:
prefix、wildcard、regexp开销大,生产环境谨慎使用。考虑使用match_phrase_prefix或 edge n-gram 替代。
fuzzy — 纠错/模糊匹配
# 允许 1 次编辑距离的错误
GET /_search
{
"query": {
"fuzzy": {
"title": {
"value": "elastc",
"fuzziness": "AUTO"
}
}
}
}| 参数 | 说明 |
|---|---|
fuzziness |
AUTO(默认,自动计算)或数字(编辑距离) |
prefix_length |
前 N 个字符必须完全匹配(提升性能) |
地理查询
geo_distance — 圆形范围
GET /restaurants/_search
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat": 40.73,
"lon": -74.1
}
}
}
}geo_bounding_box — 矩形范围
GET /restaurants/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": { "lat": 40.73, "lon": -74.1 },
"bottom_right": { "lat": 40.717, "lon": -73.99 }
}
}
}
}复合查询
bool — 多条件组合
GET /bank/_search
{
"query": {
"bool": {
"must": [
{ "match": { "state": "ND" } }
],
"must_not": [
{ "term": { "gender": "M" } }
],
"should": [
{ "term": { "city": "New York" } },
{ "term": { "city": "Boston" } }
],
"filter": [
{ "range": { "age": { "gte": 18, "lte": 65 } } },
{ "range": { "balance": { "gte": 20000 } } }
],
"minimum_should_match": 1
}
}
}| 子句 | 逻辑 | 参与评分 | 适用场景 |
|---|---|---|---|
must |
AND(必须匹配) | ✅ | 核心查询条件 |
should |
OR(选择性匹配) | ✅ | 加分条件 |
must_not |
NOT(必须不匹配) | ❌ | 排除条件 |
filter |
AND(必须匹配) | ❌ | 精确过滤(有缓存) |
query vs filter 深度对比
| 维度 | query 上下文 | filter 上下文 |
|---|---|---|
| 目的 | 做排名:匹配越好分数越高 | 做过滤:在或不在 |
| 输出 | 每个文档都有 _score |
无评分(_score: null 或 0) |
| 性能 | 计算 TF-IDF/BM25,较慢 | 不评分 + 自动缓存(Filter Cache) |
| 缓存 | 不缓存(不同查询得分不同) | 缓存 bitset,重复查询极快 |
💡 最佳实践:精确值过滤用
filter,全文相关性排序用must。
constant_score — 固定分数
将 filter 包装为 query 上下文并赋予固定分数:
GET /_search
{
"query": {
"constant_score": {
"filter": { "term": { "level": "vip" } },
"boost": 1.5
}
}
}dis_max — 最佳字段得分
取多查询中得分最高的(而非求和),适合同一个 query 搜多个字段:
GET /_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "elasticsearch" } },
{ "match": { "body": "elasticsearch" } }
],
"tie_breaker": 0.3
}
}
}🔬 深入原理:
tie_breaker(0~1)将非最高得分字段的分数乘以该系数后也纳入总分。0= 只看最高分(默认),1= 所有字段等权重加和。
boosting — 降权查询
GET /_search
{
"query": {
"boosting": {
"positive": { "match": { "title": "apple" } },
"negative": { "match": { "title": "pie" } },
"negative_boost": 0.5
}
}
}function_score — 自定义评分
修改文档的相关性算分,实现"付费推广"“热门加权"等效果。

基本结构
GET /products/_search
{
"query": {
"function_score": {
"query": { "match": { "title": "手机" } },
"functions": [
{
"filter": { "term": { "brand": "华为" } },
"weight": 2
},
{
"field_value_factor": {
"field": "sales",
"factor": 0.1,
"modifier": "log1p"
}
}
],
"boost_mode": "multiply",
"score_mode": "sum",
"max_boost": 10
}
}
}打分函数类型
| 函数 | 说明 | 示例 |
|---|---|---|
weight |
常数加权 | "weight": 2 — 匹配该 filter 的文档分数乘 2 |
field_value_factor |
用文档中某字段的值 | "field": "popularity" — 越热门的文档得分越高 |
random_score |
随机分数 | 用于随机推荐/AB 测试 |
script_score |
Painless 脚本自定义 | 完全控制评分逻辑 |
decay |
衰减函数(高斯/线性/指数) | 距离目标值越近得分越高 |
评分算法演变
| 算法 | ES 版本 | 特点 |
|---|---|---|
| TF-IDF | 5.0 之前 | 词频线性增长,无上限 |
| BM25 | 5.0 至今 | 词频增长曲线趋于水平(词频饱和度) |
🔬 深入原理:BM25 引入了词频饱和度(Term Frequency Saturation)和文档长度归一化。一个词在文档中出现 1000 次并不意味着相关性是出现 100 次的 10 倍——增长曲线会变平。
重要参数
| 参数 | 说明 | 可选值 |
|---|---|---|
boost_mode |
function score 与 query score 的运算方式 | multiply(默认)/ replace / sum / avg / max / min |
score_mode |
多个 function 之间如何合并 | multiply(默认)/ sum / avg / first / max / min |
max_boost |
限制最高加权倍数(防止极端) | 数值 |
相关度评分详解
explain — 查看评分明细
GET /bank/_explain/1
{
"query": {
"match": { "address": "mill lane" }
}
}返回每个词条对最终得分的贡献。对于复杂查询,使用 POST /bank/_search + "explain": true 对所有结果输出评分明细。
BM25 公式(简化理解)
score(q, d) = Σ IDF(qi) × TF(qi, d) / (TF(qi, d) + k1 × (1 - b + b × |d|/avgD))| 因子 | 含义 |
|---|---|
| IDF | 逆文档频率 — 词在越少文档中出现,权重越大 |
| TF | 词频 — 词在本文档中出现次数 |
| ** | d |
| avgD | 平均文档长度 |
| k1 | 词频饱和度参数(默认 1.2) |
| b | 长度归一化参数(默认 0.75,0 = 不考虑长度) |
💡 最佳实践:大多数场景不需要调整 BM25 参数。但在短文本(如商品标题)和长文本(如文章正文)混合搜索时,可调低
b值降低长度惩罚。
nested query — 嵌套对象精确查询
普通 object 类型会被扁平化,导致搜索失去关联性。nested 类型保持对象内字段的关联。
# Mapping
PUT /orders
{
"mappings": {
"properties": {
"items": {
"type": "nested",
"properties": {
"name": { "type": "keyword" },
"price": { "type": "double" }
}
}
}
}
}
# 查询:订单中同时包含 "手机"(价格<5000)和 "手机壳"(价格<50)的商品
GET /orders/_search
{
"query": {
"nested": {
"path": "items",
"query": {
"bool": {
"must": [
{ "match": { "items.name": "手机" } },
{ "range": { "items.price": { "lte": 5000 } } }
]
}
}
}
}
}| 问题 | object 类型 | nested 类型 |
|---|---|---|
| 存储 | 扁平化,丢失对象边界 | 独立 Lucene 文档,保持关联 |
| 查询 | 可能误匹配(跨对象) | 精确关联匹配 |
| 性能 | 快 | 慢(需要 join) |
search_after — 实时深度分页
详见 04-排序分页与高亮。
常见陷阱
| 陷阱 | 说明 |
|---|---|
🚨 term 对 text 字段查不到 |
text 写入时已分词,term 不做分词导致不匹配 |
🚨 match 是 OR 关系 |
"mill lane" 包含 mill 或 lane 即命中 |
🚨 filter 不参与评分 |
需要排序的场景必须用 must |
🚨 multi_match 字段越多越慢 |
权衡搜索覆盖度和性能 |
🚨 prefix/wildcard/regexp 开销大 |
全表扫描式匹配,大索引中极慢 |
| 🚨 object 嵌套查询可能误匹配 | 跨对象字段交叉匹配;用 nested 类型解决 |
🚨 hits.total 超过 10000 不精确 |
需要精确数设 track_total_hits: true |
🚨 function_score 不加 max_boost |
极端值可能让某些文档完全淹没其他结果 |