一个排序把老王打回原形
分词搞定,老王美滋滋上线了搜索。第二天运营又找上门:按价格从低到高排序,第一位是 10 元的糖浆,第二位才是 9 元的滤杯——10 怎么会比 9 小?再一查,销量聚合直接报错:Fielddata is disabled on text fields by default。老王低头一看 mapping,price 字段被建成了 text。字段类型建错,排序、聚合、精度全废,而且ES 不允许原地改类型。这一篇就把 mapping 这块地基讲透。
text 与 keyword:一对分身
字符串字段在 ES 里有两种类型,分工完全不同:
text 会分词。「燕麦拿铁(大杯)」被切成「燕麦」「拿铁」「大杯」进倒排,用于全文搜索,不用于排序和聚合。
keyword 不分词。整串原样存进正排,用于精确匹配(term)、排序、聚合。状态码、标签、品牌名这类枚举值,都该是 keyword。
一个字段能不能既要又要?可以,用多字段(fields)配置,一个 text 主字段加一个 keyword 子字段:
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}搜「拿铁」走 name,按品牌精确聚合走 name.keyword。ignore_above 超长的串不进子字段,防止正排爆内存。price 这种数值字段则老老实实建 integer 或 long,跟分词毫无关系。
动态 mapping:自作主张的实习生
老王的 price 为什么成了 text?因为他建索引时根本没写 mapping,是 ES 的动态 mapping 自作主张猜的类型。这位实习生的习惯是:
| JSON 里的样子 | 实习生猜的类型 | 备注 |
|---|---|---|
| 字符串 | text + keyword 子字段 | 老王 price 是字符串数字,被猜成 text |
| 像日期的字符串 | date | date_detection 开着就会猜 |
| 数字 | long 或 double | 纯数字字符串默认不会猜成数字 |
| true / false | boolean | 这个猜得最靠谱 |
实习生靠得住,母猪能上树。生产环境的正确姿势是显式建 mapping,并且关掉自作主张:dynamic 设 false,新字段不进索引(文档能存但搜不到);设 strict 更狠,新字段直接写入报错,逼着走变更流程。宁可写入报错让人看见,也别让字段悄悄丢在索引外。
常用类型速查与两个经典坑
日常够用的类型一张表列完:
| 类型 | 用在哪 | 常见坑 |
|---|---|---|
| text | 全文搜索 | 拿去做排序聚合必报错 |
| keyword | 精确匹配、排序、聚合 | 不分词,整串才匹配上 |
| integer / long / double | 价格、数量、分数 | 金额别用 double,精度问题换 long 存分 |
| date | 下单时间、发布时间 | 格式与时区要写进 format |
| boolean | 上下架状态 | 几乎没坑 |
| object | 扁平 JSON 对象 | 数组对象会被拍平,见下 |
| nested | 数组内保持独立 | 每个嵌套对象一个隐藏文档,成本更高 |
object 的坑值得单独演一遍。老王给商品加了评论人列表,两个对象:
"comments": [
{ "first": "张", "last": "王" },
{ "first": "李", "last": "四" }
]object 类型会把它拍平成 first: 张、李 和 last: 王、四 两组并列数组。这时查「first 是张 且 last 是四」,ES 一看两个字段里都有值,命中——可张王和李四压根不是一个人。要保持对象内部配对关系,字段类型必须是 nested:每个嵌套对象存成独立的隐藏文档,查询时用 nested 查询单独匹配。代价是写入和存储成本更高,所以只给真正需要「配对查询」的字段用。
类型建错了怎么补救
坏消息:字段的类型建了就不能改,改了会报 illegal_argument_exception。好消息:补救路线第 4 篇已经铺好了——
第一步:按正确类型新建 product_v2 的 mapping
第二步:POST _reindex 搬数据
第三步:别名 product 从 v1 切到 v2数据量大时 _reindex 慢,记得带上 slices 参数并行切分,或者用外部脚本双写迁移。预防永远比补救便宜:mapping 写进代码仓库走评审,上线前用几条真实文档试写试查,别把类型判断交给动态 mapping。
小结
mapping 一句话:类型定生死,text 管搜索,keyword 管排序聚合,动态 mapping 不可信,nested 救数组配对。建错类型的账,迟早连本带利还——reindex 就是那条还债的路。
类型理顺了,老王又撞上新问题:文档刚写进去,搜索却查不到,过一秒才出来。这不是 bug,是 ES 的近实时设计。下一篇讲 refresh 与段:为什么搜不到刚写进去的文档,以及 ES 怎么做到写入不丢。
评论 (0)