ES Mapping 设计:字段类型决定查询能力
1. 核心字段类型对比
| 类型 | 用途 | 是否分词 | 适用场景 | 为什么这样设计 |
|---|---|---|---|---|
text | 全文检索 | ✅ 是 | 文章内容、商品描述 | 分词后建立倒排索引,基于分析器生成的词项做全文检索 |
keyword | 精确匹配、排序、聚合 | ❌ 否 | 状态码、标签、ID、业务标识符 | 不分词,整体作为一个词项,用于精确匹配、排序和聚合 |
integer/long | 数值 | ❌ 否 | 价格、数量、年龄 | 数值类型支持范围查询和排序 |
date | 日期 | ❌ 否 | 创建时间、更新时间 | 日期类型支持时间范围查询 |
nested | 嵌套对象 | - | 订单中的商品列表 | 普通对象数组会被扁平化,导致不同对象的字段被错误组合;nested 把每个对象作为独立文档处理,保证查询字段来自同一对象 |
2. 工作中常见错误(深度分析)
json
// ❌ 错误:对 text 类型用 term 做精确匹配,通常查不到预期结果
{
"query": {
"term": { "title": "Java工程师" }
}
}
// 原因:title 是 text 类型,索引时会经过分析器,只有分析结果中的词项才参与全文匹配。
// term 查询不会像 match 那样对查询值做全文检索分析,而是直接拿 "Java工程师" 去匹配词项。
// 以 ik_max_word 为例,它通常把 "Java工程师" 分成 ["Java","工程师"],且不保留完整的 "Java工程师" 词项,
// 于是 term 查 "Java工程师" 就匹配不到。分词结果取决于 IK 版本、词典和配置,不一定是固定结果。
// ✅ 正确:全文检索用 match,精确匹配用 keyword 字段
// Mapping 中同时定义两种类型(multi-field)
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": { "type": "keyword" }
}
}
}
}
}
// 全文检索:match 用查询分析器处理查询值,默认通常沿用字段的索引分析器
{
"query": { "match": { "title": "Java工程师" } }
}
// 精确匹配:term 直接匹配 keyword 字段的完整词项
{
"query": { "term": { "title.keyword": "Java工程师" } }
}3. 动态 Mapping 与字段变更
动态 Mapping 与字段变更:ES 会自动推断字段类型,但可能推断错误。字符串默认映射为
text并附带keyword子字段;启用numeric_detection后,数字字符串如"123"可能被识别为相应的数值类型(如long),具体类型取决于动态类型推断规则。字段被映射为数值类型后,查询语义随之改变:仍能按数值匹配和做范围查询,但不能再按 keyword 字符串语义做精确匹配。已有字段的类型不能通过更新 Mapping 直接改成另一种类型。Mapping 的可修改范围:可以新增字段、为已有字段增加多字段(multi-field),部分参数(如
ignore_above)也允许更新,但可更新性取决于具体参数和 ES 版本,且修改不会回溯重建已有文档的索引数据。尤其要注意:新增 keyword 多字段后,历史文档不会自动补齐该字段的索引数据,需通过重新处理已有文档(Reindex 或 update 触发重新索引)才能按新字段查询或聚合。动态 Mapping 自动生成的 keyword 子字段通常默认ignore_above: 256,超过该阈值的字段值不会被索引到该 keyword 字段,但_source中的原始值不会丢失。详见 索引生命周期管理 中的 Reindex 操作。生产上,业务索引(订单、商品、用户等)应显式定义关键字段的 Mapping,尤其是业务标识符、金额、时间和需要聚合的字段;对字段结构变化较大的日志或事件数据,可用动态模板限制自动推断范围。是否手动定义 Mapping,取决于业务对字段类型稳定性和查询语义的要求,而不是无差别地「务必手动定义」。
4. 设计检查清单
定义字段前,先确认下面几点,避免把字段类型定错、改了又没法直接回退:
- 这个字段是否同时需要全文检索和完整字符串匹配?需要的话定义
text并加keyword子字段。 - 业务 ID、订单号是否只是标识符、没有数值运算语义(如需要保留前导零、按字符串匹配)?是则定义为
keyword,而不是数值类型。 - 启用动态 Mapping 时,不同索引中的同名字段是否可能因各自首次写入的数据不同而被推断为不同类型?
- 对象数组的查询是否要求字段来自同一个对象?要求的话用
nested。 - 新增多字段后,历史文档是否需要按新字段查询或聚合?需要的话规划重新处理(Reindex 或 update)。