Elasticsearch 查询核心概念
目录
- doc_values
- fielddata
- [doc_values vs fielddata 对比](#doc_values vs fielddata 对比)
- [global ordinals](#global ordinals)
- collapse(字段折叠)
- track_total_hits
1. doc_values
概念
doc_values 是 Elasticsearch 在索引时 (index time)构建的一种列式存储(column-oriented store)数据结构,存储在磁盘上。
它以文档 → 字段值的映射方式存储数据,但在物理层面是按列组织的,非常适合需要"遍历某个字段所有文档值"的操作。
工作原理
倒排索引(用于全文搜索):
"apple" → [doc1, doc3, doc7]
"banana" → [doc2, doc5]
doc_values(列式存储,用于聚合/排序):
doc1 → "apple"
doc2 → "banana"
doc3 → "apple"
支持的字段类型
| 支持 doc_values | 不支持 doc_values |
|---|---|
| keyword | text |
| numeric(long/integer/float...) | annotated_text |
| date | |
| boolean | |
| ip | |
| geo_point |
text 类型不支持 doc_values,因为 text 经过分词,原始值已碎片化。
使用场景
- 排序(sort):按某字段排序文档
- 聚合(aggregations):terms agg、range agg、avg/sum 等
- 脚本访问(script) :在脚本中通过
doc['field'].value访问
配置示例
json
PUT /my_index
{
"mappings": {
"properties": {
"price": {
"type": "double",
"doc_values": true // 默认为 true,可显式关闭
},
"description": {
"type": "keyword",
"doc_values": false // 若不需要排序/聚合,关闭可节省磁盘
}
}
}
}
性能特点
- 磁盘存储:持久化到磁盘,OS 缓存负责热数据加速
- 堆内存友好:不占用 JVM 堆,适合大数据量场景
- 索引速度略降:需要在写入时额外构建列式结构
2. fielddata
概念
fielddata 是 Elasticsearch 针对 text 类型字段的一种**运行时(query time)**内存数据结构,用于支持对 text 字段进行排序和聚合。
由于 text 字段经过分词,无法直接使用 doc_values,因此 ES 在查询时将倒排索引"反转"成列式结构,加载到 JVM 堆内存中。
为什么默认禁用
- 对 text 字段做聚合/排序通常语义错误(你可能想要的是原始值而非分词后的 token)
- 首次加载时需要全量扫描分片,延迟极高
- 驻留在 JVM 堆,内存压力大,可能触发 OOM 或 GC 抖动
启用方式
json
PUT /my_index/_mapping
{
"properties": {
"content": {
"type": "text",
"fielddata": true // 慎用!明确知道自己在做什么时才开启
}
}
}
推荐替代方案
与其开启 text 的 fielddata,更推荐:
json
{
"properties": {
"title": {
"type": "text", // 用于全文搜索
"fields": {
"keyword": {
"type": "keyword" // 用于聚合/排序,利用 doc_values
}
}
}
}
}
查询时使用 title.keyword 进行聚合或排序。
fielddata 内存控制
yaml
# elasticsearch.yml
indices.fielddata.cache.size: 20% # 限制 fielddata 缓存最大占堆比例
超出限制时,ES 会驱逐最久未使用的 fielddata 缓存。
3. doc_values vs fielddata 对比
| 维度 | doc_values | fielddata |
|---|---|---|
| 构建时机 | 索引时(index time) | 查询时(query time) |
| 存储位置 | 磁盘(OS page cache 加速) | JVM 堆内存 |
| 适用字段 | keyword、numeric、date 等 | text(不推荐) |
| 默认状态 | 默认开启 | 默认关闭 |
| 内存影响 | 几乎不占堆 | 直接占用 JVM 堆 |
| 首次访问延迟 | 低(已在磁盘) | 高(需运行时构建) |
| 推荐程度 | ✅ 推荐 | ⚠️ 慎用 |
核心结论 :能用 doc_values 就用 doc_values;text 字段需要聚合时,用 fields 多字段映射加一个 keyword 子字段。
4. global ordinals
概念
global ordinals 是 Elasticsearch 在 keyword 字段(以及开启 fielddata 的 text 字段)上建立的一种全局序号映射 ,用于加速 terms aggregation 等操作。
问题背景
Lucene 段(segment)级别存在局部序号(local ordinals):
segment 1: { "apple"→0, "banana"→1, "cherry"→2 }
segment 2: { "apple"→0, "date"→1 }
跨段做 terms aggregation 时,需要将各段的 local ordinals 映射到统一的全局序号,这就是 global ordinals 的作用:
global ordinals:
"apple" → 0
"banana" → 1
"cherry" → 2
"date" → 3
构建时机
- 默认(lazy 加载):第一次对该字段执行 terms aggregation 时,ES 构建 global ordinals,并缓存在内存中(堆外,使用 Lucene 的 Off-Heap 结构)
- eager_global_ordinals:可配置为在 refresh 时立即构建,适合高频聚合场景
配置示例
json
PUT /my_index
{
"mappings": {
"properties": {
"category": {
"type": "keyword",
"eager_global_ordinals": true // refresh 时立即构建,减少首次查询延迟
}
}
}
}
性能影响
| 场景 | 建议 |
|---|---|
| 高基数字段(cardinality 高,如 user_id) | 谨慎使用 eager,构建成本高 |
| 低基数字段(如 status、category) | 推荐 eager_global_ordinals |
| 写多读少(如日志索引) | 不必 eager |
| 读多写少(如维度表) | eager_global_ordinals 收益显著 |
与 terms aggregation 的关系
Terms aggregation 的核心流程:
1. 利用 global ordinals 将每个文档的字段值映射为整数 ID
2. 用整数数组统计每个 ordinal 的文档数
3. 选出 top-N ordinals
4. 将 ordinal 反查回原始字符串值
使用整数运算替代字符串比较,大幅提升聚合性能。
5. collapse(字段折叠)
概念
collapse 是 Elasticsearch 的搜索去重 功能,允许在搜索结果中按某个字段的值折叠(去重),每个唯一值只保留得分最高的文档。
类似于 SQL 中的 GROUP BY + 取最优记录,但底层实现完全不同。
基础用法
json
GET /my_index/_search
{
"query": {
"match": { "title": "elasticsearch" }
},
"collapse": {
"field": "user_id" // 按 user_id 折叠,每个用户只返回一条
},
"sort": [
{ "score": "desc" } // 保留得分最高的文档
]
}
inner_hits:展开被折叠的文档
json
GET /my_index/_search
{
"collapse": {
"field": "user_id",
"inner_hits": {
"name": "top_posts", // inner_hits 的名称
"size": 3, // 每个折叠组展开显示 3 条
"sort": [{ "date": "desc" }]
}
}
}
返回结构:
json
{
"hits": [
{
"_source": { "user_id": "u1", "title": "..." },
"inner_hits": {
"top_posts": {
"hits": { "hits": [ ... ] } // 该 user 的 top 3 文档
}
}
}
]
}
多级 collapse(ES 7.x+)
json
{
"collapse": {
"field": "user_id",
"inner_hits": {
"name": "by_category",
"collapse": { "field": "category" }, // 二级折叠
"size": 5
}
}
}
约束与限制
| 限制项 | 说明 |
|---|---|
| 字段要求 | 折叠字段必须是 keyword、numeric 或 date 类型,且开启 doc_values |
| 不影响聚合 | total_hits 统计的是折叠前的文档总数,聚合也在折叠前的文档集上执行 |
| 不支持 scroll | collapse 与 scroll API 不兼容 |
| 不支持 rescore | collapse 后不能再 rescore |
| 分页行为 | from + size 针对折叠后的结果计数 |
典型应用场景
- 商品搜索:同一 SPU 下多个 SKU,只展示最相关的一个
- 新闻聚合:同一话题/来源只展示一篇
- 用户行为:同一用户的多条日志只展示最新/最高分一条
6. track_total_hits
概念
track_total_hits 控制 Elasticsearch 在搜索响应中如何统计命中总数 (hits.total)。
背景
精确统计文档总数(如:共找到 1,234,567 条结果)在大数据集上代价很高,因为 ES 需要遍历所有匹配文档。Elasticsearch 7.0 起引入此参数来提供灵活控制。
参数取值
| 值 | 类型 | 含义 |
|---|---|---|
true |
boolean | 精确统计,返回真实 total(relation: "eq") |
false |
boolean | 完全不统计,total 返回 -1(性能最优) |
N(正整数) |
integer | 当总数 ≤ N 时精确统计;超过 N 时返回 ≥ N 的近似值(relation: "gte") |
默认值 :10000(ES 7.0+)
示例
json
GET /my_index/_search
{
"track_total_hits": true, // 精确统计所有文档
"query": { "match_all": {} }
}
响应:
json
{
"hits": {
"total": {
"value": 1234567,
"relation": "eq" // 精确值
}
}
}
使用默认值(10000)时,若超过 10000:
json
{
"hits": {
"total": {
"value": 10000,
"relation": "gte" // 表示"至少 10000 条"
}
}
}
性能影响
track_total_hits=false → 跳过 total 统计,最快
track_total_hits=10000 → 默认,超过阈值后停止精确计数
track_total_hits=true → 全量遍历,数据量大时最慢
实践建议
| 场景 | 推荐配置 |
|---|---|
| 只关心前几页结果,不显示"共 X 条" | false 或较小的整数 |
| 显示"约 X 条"近似数即可 | 默认 10000 |
| 需要精确分页总数(如后台管理) | true |
| 深度分页 + 大数据集 | 用 search_after 替代传统分页,配合 false |
综合对比速查表
| 概念 | 阶段 | 核心作用 | 存储位置 | 注意事项 |
|---|---|---|---|---|
| doc_values | 索引时 | 排序、聚合、脚本访问 | 磁盘 | 默认开启,text 不支持 |
| fielddata | 查询时 | text 字段聚合/排序 | JVM 堆 | 慎用,推荐用 keyword 子字段替代 |
| global ordinals | Refresh 时 / 首次查询时 | 加速 terms aggregation | 内存(Off-Heap) | 高基数字段谨慎开启 eager |
| collapse | 查询时 | 搜索结果去重/折叠 | --- | 字段需开启 doc_values |
| track_total_hits | 查询时 | 控制 total 统计精度 | --- | 大数据集避免设为 true |