Elasticsearch 索引 mapping 设计中的字段类型选择建议
前段时间帮朋友排查一个 ES 集群的问题,搜索接口动不动就超时,打开 mapping 一看,好家伙,连用户 ID、订单状态、手机号这种字段全是 text,查询要套 match,聚合直接没得做。后来新建索引把类型捋了一遍,问题基本就没了。所以说 mapping 这东西,建索引之前多花十分钟想清楚,能省后面一堆麻烦。下面是一些我踩坑之后总结的类型选择建议,不一定全对,但都是实测有感的。
先想清楚一件事:mapping 定了就改不了
ES 的字段类型一旦定了,就没法原地修改,只能新建索引然后 reindex,数据量大了这个过程是真的煎熬,期间还要占双倍存储。所以每次建索引之前,先看看数据长什么样、以后大概会怎么查,比事后补救划算多了。
text 和 keyword,分清楚再用
这是最多人搞混的地方,没有之一。
text 会分词,适合全文检索那种“搜内容”的场景,比如文章正文、商品描述;keyword 不分词,整个字段当一个 token,适合精确匹配、排序和聚合,比如状态码、标签、手机号、邮箱。
常见的坑是这样来的:动态 mapping 下字符串默认会生成 text 加一个 .keyword 子字段,看起来两全其美,但如果某个字段永远只需要精确匹配(比如各种 ID),直接显式声明成 keyword 就行,还能省掉分词的开销。反过来,如果你用的是自动生成的子字段,查询时记得写 field.keyword,不然匹配行为会跟你预期的不一样,这个坑我见过不止一个人踩。
拿不准的话,多字段是最稳妥的方案:
"title": {
"type": "text",
"fields": {
"raw": { "type": "keyword", "ignore_above": 256 }
}
}
ignore_above 建议加上,超过长度的就不建索引,省得一些超长脏数据把索引撑得莫名其妙。
数值类型:别无脑上 double
整型没什么好说的,范围够用就行,integer 比 long 省一点是一点。
浮点是重灾区。double 精度高但占空间,很多场景其实 scaled_float 更合适,比如价格,用 scaling_factor: 100 存,聚合和精度都有保障;精度要求不高的占比、评分之类,half_float 也能省不少磁盘。金额这种东西我的建议是干脆存最小单位整型(比如分),应用层去转换,免得浮点误差扯皮。
另外动态 mapping 对数字的猜测有时候很迷:第一条数据没带小数点就猜成 long,后面来一条带小数点的直接写入报错;第一条带了小数点就猜成 float,后面的整型也跟着按 float 存……所以重要索引还是老实手动定义 mapping,或者把 dynamic 设成 strict,新字段乱进来直接报错,至少问题能在写入阶段暴露出来,而不是查询的时候才发现。
date 类型记得指定 format
date 默认只认 ISO8601 那几种格式,数据源要是 yyyy/MM/dd 或者毫秒时间戳这种,不指定 format 会直接写入失败。时间戳的话把 epoch_millis 写进 format 列表就行:
"create_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
}
用 || 连接多个格式,写入时按顺序匹配。这东西不难,但每次都是等报错了才想起来。
数组套对象,用 nested 而不是 object
JSON 数组默认按 object 处理,问题在于 ES 会把内部字段拍平,各对象同名字段会合并到一起。经典例子:订单里存多个商品,每个商品有名称和数量,用 object 存的时候,查“名称A 且 数量1”,可能匹配到“名称A 在一个对象里、数量1 在另一个对象里”的文档,结果就是一堆看起来毫无关联的订单也被查出来了。
会出这种交叉匹配的,改成 nested。代价是每个 nested 对象是独立的隐藏文档,更新成本高一些,也别无限嵌套,默认 nested_fields 上限是 50,一般够用。
用不到的字段,别让它白白吃资源
有些字段只存不用,比如一段很长的日志原文,只是落库备份,从来没搜过,这种可以设 "index": false;连排序聚合都不需要,再配 "doc_values": false。结构不固定的 JSON 可以整个 enabled: false,只存不解析。_source 一般不建议关,reindex、高亮、结果回显都靠它。
顺带一提,IP 用 ip,坐标用 geo_point,别拿 keyword 和数组凑合,不然以后要按网段过滤或者按距离排序的时候是真没辙。
写在最后
总结下来就几句话:要精确匹配的用 keyword,要分词的才用 text;金额用整型或 scaled_float;时间指定好 format;嵌套对象看情况上 nested;没用的字段别建索引。然后把写好的 mapping 放进 index template 里,别让动态 mapping 自由发挥,后面能少掉一堆奇奇怪怪的问题。
要是拿不准某个字段到底选什么,最笨但最有效的办法是先拿几条真实数据试写一下,看看 _analyze 和 _mapping 的实际表现再定,比拍脑袋靠谱。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。