1.概述
elasticsearch在当下互联网系统中使用非常广泛,是一个非常热门的组件框架。那elasticsearch到底是什么呢?
elasticsearch 是一个分布式、实时的搜索和分析引擎,通常用于处理和查询大规模的结构化与非结构化数据。它基于 Apache Lucene 构建,具有高效的全文搜索、数据存储、数据分析和数据可视化功能。elasticsearch 主要应用于日志分析、监控、数据检索、实时分析等场景。
官方文档地址:https://www.elastic.co/
2.映射mapping
在 elasticsearch 中,mapping(映射)是指为索引中的字段定义结构、类型和规则。类似于关系型数据库中的表结构,映射决定了 elasticsearch 如何存储和索引数据,其作用和关系型数据库定义表字段差不多:
定义字段类型:例如字符串、数字、日期等。
控制字段行为:决定字段是否进行分词、是否索引、如何排序等。
优化查询性能:正确的映射能提高查询效率,减少不必要的存储。
数据验证:映射可以确保写入的文档符合预期的数据结构和类型。
映射mapping分为:动态映射和静态映射
2.1 动态映射
顾名思义,就是自动创建出来的映射。es 根据存入的文档,自动分析出来文档中字段的类型以及存储方式,这种就是动态映射。示例如下所示:
首先,先定义一个索引,名为user_info
PUT user_info
一个简单的DSL语句就建立了一个索引,不太清楚的先跳转上面的链接入门学习下哈。
紧接着我们直接插入一条es文档数据
PUT user_info/_doc/1
{
"id":1,
"name":"张三",
"amount":88.99
}
插入成功,再来看看索引user_info的mapping定义:
GET user_info/_mapping
查询结果如下:
{
"user_info" : {
"mappings" : {
"properties" : {
"amount" : {
"type" : "float"
},
"id" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
可以看到,es在我们插入文档数据时自动检测字段类型动态定义了mapping结构。
2.2 静态映射
静态映射就和关系型数据库表结构加字段一样,在插入es文档数据之前,先定义字段mapping再插入数据,比如我们往user_info添加一个身份证号idCard:
PUT user_info/_mapping
{
"properties": {
"idCard": {
"type": "keyword"
}
}
}
keyword关键字类型代表不分词,elasticsearch会对text字段进行分词实现全文检索,但是身份证并不需要分词,它是一个整体属性字段,如果是通过动态映射生成mapping定义,那么会和上面的name字段一样:
"idCard" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
这里我解释一下这个mapping的定义:其中既包含 text 类型字段用于全文搜索,也包含 keyword 字段用于精确匹配和聚合。这种设计方式可以在同一个字段上实现不同类型的查询
text 类型用于全文搜索(如 matchQuery),Elasticsearch 会对 text 类型字段进行分词(默认使用标准分词器)。
keyword是这是 idCard 字段的子字段类型,用于精确匹配和聚合。
keyword 类型的字段不会被分词,适合存储如ID、状态、标签等字符串数据。
**ignore_above: 256**:如果字段的长度超过 256 个字符,将会被忽略,不会被索引
这种组合让你可以在同一个字段上支持全文搜索和精确匹配,从而满足不同查询需求。但实际上name字段也是并不需要分词的,直接定义keyword更加合理,性能更好。分词是一个相对比较耗费性能的操作。
由此可见动态映射也并不是万能可行的,我个人建议是:
我们在创建真正索引之前,可以先随便创建一个测试索引,然后写入测试的es文档数据,然后查看测试索引生成的mapping,这样后面我们在创建真正索引设置mapping时,就可以在前面得到的mapping基础上修改,不需要从无到有一个一个字段地开始写mapping,极大提高效率。如果是往索引添加新字段时,我觉得还是严格按照和关系型数据库表结构加字段一样,采用静态映射插入数据前,先手动添加字段mapping定义,杜绝动态映射带来的类型不匹配、性能降低等问题。
3.使用数组Array实现一对多查询
在平时业务系统表单列表搜索中,一对多查询是非常常见的,比如说,一个用户有多个标签,有多个角色,这时候我们会搜索某些标签或者某些角色下有哪些用户,是不是很常见呀???话不多说开干,我这里以标签展示,首先我们按照建议先通过静态映射在索引user_info中添加一个新字段tag存储标签id:
PUT user_info/_mapping
{
"properties": {
"tag": {
"type": "long"
}
}
}
接下来我们就可以插入数据了。这时候你可能会问,你这个定义tag字段是long类型,并没有定义array类型呀???这就可以插入数组多个数据了?是的,elasticsearch关于mapping字段介绍时,明确提到你无需专门声明某字段为数组类型,任何字段都可以隐式地存储为数组。但是要求数组中的所有值必须具有相同的数据类型。不支持混合数据类型的数组: [10,“ some string”],插入数据示例:
PUT user_info/_doc/10
{
"id":10,
"name":"张三2",
"tag":[1,2,3]
}
PUT user_info/_doc/11
{
"id":11,
"name":"张三3",
"tag":[3,4]
}
PUT user_info/_doc/12
{
"id":12,
"name":"张三4",
"tag":[4,8,9]
}
PUT user_info/_doc/13
{
"id":13,
"name":"张三5",
"tag":[1,6]
}
然后我们来搜索试试吧,搜索有标签id为3或者4的用户:
GET user_info/_search
{
"query": {
"terms": {
"tag": [3,4]
}
}
}
查询结果id=10,11,12都命中了。
Java查询实现代码:
boolQueryBuilder.must(QueryBuilders.termsQuery("tag", tagIds));
关于Spring Boot整合restHighLevelClient实现es相关操作,请查看上面的入门篇
如果要查询同时包含标签id为3和4的员工:
GET user_info/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"tag": {
"value": 3
}
}
},
{
"term": {
"tag": {
"value": 4
}
}
}
]
}
}
}
只命中id=11这一条数据
Java实现代码:
for(tagId : tagIds) {
boolQueryBuilder.must(QueryBuilders.termQuery("tag", tagId));
}
数组Array的限制:无法建立数组元素之间的关系
PUT orders/_doc/1
{
"products": ["laptop", "mouse"],
"quantities": [1, 2]
}
假设你想查询购买了 "laptop" 且数量为 1 的订单。这在 Elasticsearch 的数组查询中无法实现,因为 Elasticsearch 无法判断 "laptop" 和 1 是相关联的(它们只被视为两个独立的数组字段)。
解决方案:如果你需要这种关联关系,应使用 nested 类型来代替数组字段。后面会讲。
总的来说,数组(Arrays)字段类型适用于简单的多值字段,如标签、兴趣、角色等场景。使用简单、查询高效、
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。
4.使用Flattened存储动态字段
Flattened 类型是 Elasticsearch 7.3 引入的一种特殊的字段类型,主要用于处理层级结构(嵌套结构)数据,但以扁平化方式存储,以节省内存并提高查询效率。它特别适用于存储动态的或高可变的键值对数据,例如日志、自定义字段等。
该类型会将嵌套 JSON 对象的键值扁平化为单一层级字段。开搞!!!先定一个字段存储用户附加信息:
PUT user_info/_mapping
{
"properties": {
"extendInfo": {
"type": "flattened"
}
}
}
插入数据,我们在上面的数据更新字段extendInfo信息:
POST user_info/_update/10
{
"doc":{
"extendInfo":{
"age":18,
"gender":"女",
"height":180,
"weight":"68kg"
}
}
}
POST user_info/_update/11
{
"doc":{
"extendInfo":{
"age":30,
"gender":"女"
}
}
}
POST user_info/_update/12
{
"doc":{
"extendInfo":{
"height":170,
"weight":"58kg",
"hobby":"篮球"
}
}
}
POST user_info/_update/13
{
"doc":{
"extendInfo":{
"address":"杭州",
"phone":"12334464"
}
}
}
可以看到我们插入的extendInfo的信息都不太一样,主打就是一个动态字段信息。接下来查询看看:
GET user_info/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"extendInfo.gender": {
"value": "女"
}
}
},
{
"term": {
"extendInfo.age": {
"value": 18
}
}
}
]
}
}
}
查询结果会命中id=10这条数据
flattened 类型是一种折中的选择,适合存储动态键值对数据,如日志、标签和指标等。它提供了比 object 类型更好的性能,并且比 nested 类型占用更少的资源。然而,它无法进行复杂的嵌套关联查询和深层聚合,因此需要根据业务需求进行合理选择。
5.使用Nested存储对象数组
Nested:嵌套类型,有点千呼万唤始出来的感觉,他到底能干嘛呢?
nested 类型允许 Elasticsearch 以“独立文档”方式存储每个嵌套对象,并维持每个对象内字段之间的关系。这样可以确保查询到的数据和文档结构一致,避免出现跨对象匹配的情况。
上面我们提到数据Array类型的限制是无法建立两个数组字段直接的关系,而Flattened是存储一个对象动态字段,Nested就是可以存储数据对象,并能维护数组对象之间关系,来看看示例,一个用户有多个家人family:
PUT user_info/_mapping
{
"properties": {
"family": {
"type": "nested"
}
}
}
写入数据,基于上面的数据写入family即可:
POST user_info/_update/10
{
"doc":{
"family":[
{
"name":"李四",
"relation":"父子",
"amount":10000
},
{
"name":"王婆",
"relation":"母子",
"amount":6000
}
]
}
}
POST user_info/_update/11
{
"doc":{
"family":[
{
"name":"李四",
"relation":"兄弟",
"amount":10000
},
{
"name":"小红",
"relation":"夫妻",
"amount":1000
}
]
}
}
POST user_info/_update/12
{
"doc":{
"family":[
{
"name":"王五",
"relation":"父子",
"amount":10000
},
{
"name":"小兰",
"relation":"兄妹",
"amount":1000
}
]
}
}
接下来我们查询 relation=父子 and amount=10000的数据:
GET user_info/_search
{
"query": {
"nested": {
"path": "family",
"query": {
"bool": {
"must": [
{
"term": {
"family.relation.keyword": {
"value": "父子"
}
}
},
{
"term": {
"family.amount": {
"value": 10000
}
}
}
]
}
}
}
}
}
查询结果命中了id=10,12两条数据。你可能注意到查询family.relation后面接了keyword,这是因为我们嵌套字段对象属性是动态生成的,family.relation自动映射成text,对text字段进行term精确匹配,必须接上关键字keyword
nested 类型在存储和查询时比普通 Flattened 类型更耗资源,因为每个嵌套对象会被当作单独文档存储和索引,并且文档不能直接更新嵌套对象的部分字段,通常需要重建整个嵌套对象。
nested 类型适合嵌套对象数组的结构化数据,支持精确的对象内匹配查询和聚合。当嵌套对象只需要简单查询、没有严格的对象内关联需求时,使用 Flattened 类型更高效。
6.总结
上面我们讲了各自的定义、使用、局限性,最后这里总结概括下:
字段类型 | 适用场景 | 优点 | 缺点 |
Array | 适合简单的多值字段(如标签、爱好) | 易用、性能好 | 无法维护元素之间的关系 |
Flattened | 动态键值对结构(如可变的配置、元数据) | 占用存储空间少、动态字段支持好 | 不支持嵌套关系查询 |
Nested | 多层级结构对象,且需要查询对象内部关系 | 支持复杂的关系查询 | 性能和存储开销大 |
简单来说:
- Array 简单快捷,适合不需要关系的多值字段。
- Flattened 提供了一种高效存储动态键值对的方式,但不适合复杂查询。
- Nested 适合复杂的嵌套关系查询,但会增加存储和查询的开销。
根据你的业务需求,合理选择字段类型可以显著提升系统的查询性能和数据管理效率。