美文网首页
elasticsearch之六Mapping映射

elasticsearch之六Mapping映射

作者: Java及SpringBoot | 来源:发表于2020-02-17 17:01 被阅读0次

    个人专题目录


    1. Mapping映射入门

    1.1 什么是mapping映射

    概念:自动或手动为index中的_doc建立的一种数据结构和相关配置,简称为mapping映射。Mapping决定了index中的field的特征。

    插入几条数据,让es自动为我们建立一个索引

    PUT /website/_doc/1
    {
      "post_date": "2019-01-01",
      "title": "my first article",
      "content": "this is my first article in this website",
      "author_id": 11400
    }
    
    PUT /website/_doc/2
    {
      "post_date": "2019-01-02",
      "title": "my second article",
      "content": "this is my second article in this website",
      "author_id": 11400
    }
     
    PUT /website/_doc/3
    {
      "post_date": "2019-01-03",
      "title": "my third article",
      "content": "this is my third article in this website",
      "author_id": 11400
    }
    

    动态映射:dynamic mapping,自动为我们建立index,以及对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置。

    重点:我们当然,后面会讲解,也可以手动在创建数据之前,先创建index,以及对应的mapping

    GET  /website/_mapping/
    {
      "website" : {
        "mappings" : {
          "properties" : {
            "author_id" : {
              "type" : "long"
            },
            "content" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "post_date" : {
              "type" : "date"
            },
            "title" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
    

    尝试各种搜索

    GET /website/_search?q=2019        0条结果             
    GET /website/_search?q=2019-01-01           1条结果
    GET /website/_search?q=post_date:2019-01-01     1条结果
    GET /website/_search?q=post_date:2019          0 条结果
    

    搜索结果为什么不一致,因为es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。所以出现了_all field和post_date field的搜索表现完全不一样。

    1.2 精确匹配与全文搜索的对比分析

    exact value 精确匹配

    2019-01-01,exact value,搜索的时候,必须输入2019-01-01,才能搜索出来

    如果你输入一个01,是搜索不出来的

    select * from book where name= 'java'

    full text 全文检索

    搜“笔记电脑”,笔记本电脑词条会不会出现。

    select * from book where name like '%java%'

    (1)缩写 vs. 全称:cn vs. china

    (2)格式转化:like liked likes

    (3)大小写:Tom vs tom

    (4)同义词:like vs love

    2019-01-01,2019 01 01,搜索2019,或者01,都可以搜索出来

    china,搜索cn,也可以将china搜索出来

    likes,搜索like,也可以将likes搜索出来

    Tom,搜索tom,也可以将Tom搜索出来

    like,搜索love,同义词,也可以将like搜索出来

    就不是说单纯的只是匹配完整的一个值,而是可以对值进行拆分词语后(分词)进行匹配,也可以通过缩写、时态、大小写、同义词等进行匹配。深入 NPL,自然语义处理。

    1.3 全文检索下倒排索引核心原理快速揭秘

    重建倒排索引

    normalization正规化,建立倒排索引的时候,会执行一个操作,也就是说对拆分出的各个单词进行相应的处理,以提升后面搜索的时候能够搜索到相关联的文档的概率

    时态的转换,单复数的转换,同义词的转换,大小写的转换

    mom ―> mother

    liked ―> like

    small ―> little

    dogs ―> dog

    1.4 query string根据字段分词策略

    query string必须以和index建立时相同的analyzer进行分词

    query string对exact value和full text的区别对待

    如: date:exact value 精确匹配

    ​ text: full text 全文检索

    1.5 mapping总结

    1. 往es里面直接插入数据,es会自动建立索引,同时建立对应的mapping。(dynamic mapping)

    2. mapping中就自动定义了每个field的数据类型

    3. 不同的数据类型(比如说text和date),可能有的是exact value,有的是full text

    4. exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;full text,会经历各种各样的处理,分词,normaliztion(时态转换,同义词转换,大小写转换),才会建立到倒排索引中。

    5. 同时,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索

    6. 可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型;也可以提前手动创建index和tmapping,自己对各个field进行设置,包括数据类型,包括索引行为,包括分词器,等。

    1.6 mapping的核心数据类型以及dynamic mapping

    核心的数据类型

    字符型:string,string类型包括
    text 和 keyword
    
    text类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许es来检索这些词语。text类型不能用来排序和聚合。
    
    Keyword类型不需要进行分词,可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索
    
    数字型:long, integer, short, byte, double, float
    日期型:date
    布尔型:boolean
    二进制型:binary
    

    详见:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/mapping-types.html

    复杂数据类型(Complex datatypes)

    数组类型(Array datatype):数组类型不需要专门指定数组元素的type,例如:
        字符型数组: [ "one", "two" ]
        整型数组:[ 1, 2 ]
        数组型数组:[ 1, [ 2, 3 ]] 等价于[ 1, 2, 3 ]
        对象数组:[ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }]
    对象类型(Object datatype):_ object _ 用于单个JSON对象;
    嵌套类型(Nested datatype):_ nested _ 用于JSON数组;
    
    

    地理位置类型(Geo datatypes)

    地理坐标类型(Geo-point datatype):_ geo_point _ 用于经纬度坐标;
    地理形状类型(Geo-Shape datatype):_ geo_shape _ 用于类似于多边形的复杂形状;
    

    特定类型(Specialised datatypes)

    IPv4 类型(IPv4 datatype):_ ip _ 用于IPv4 地址;
    Completion 类型(Completion datatype):_ completion _提供自动补全建议;
    Token count 类型(Token count datatype):_ token_count _ 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少。
    mapper-murmur3
    类型:通过插件,可以通过 _ murmur3 _ 来计算 index 的 hash 值;
    附加类型(Attachment datatype):采用 mapper-attachments
    插件,可支持_ attachments _ 索引,例如 Microsoft Office 格式,Open Document 格式,ePub, HTML 等。
    

    dynamic mapping 推测规则

    true or false -> boolean

    123 -> long

    123.123 -> double

    2018-01-01 -> date

    hello world -> text

    [] -> array

    {} -> object

    在上述的自动mapping字段类型分配的时候,只有text类型的字段需要分词器。默认分词器是standard分词器。

    查看mapping

    GET /index/_mapping/

    {
      "test_index": { # 索引名
        "mappings": { # 映射列表
          "test_type": { # 类型名
            "properties": { # 字段列表
              "age": { # 字段名
                "type": "long" # 字段类型
              },
              "gender": {
                "type": "text",
                "fields": { # 子字段列表
                  "keyword": { # 子字段名
                    "type": "keyword", # 子字段类型,keyword不进行分词处理的文本类型
                    "ignore_above": 256 # 子字段存储数据长度
                  }
                }
              },
              "name": {
                "type": "text",
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        }
      }
    }
    

    1.7 手动管理mapping

    查询所有索引的映射

    GET /_mapping

    创建映射

    创建索引后,应该立即手动创建映射

    PUT book/_mapping
    {
        "properties": {
               "name": {
                      "type": "text"
                },
               "description": {
                  "type": "text",
                  "analyzer":"english",
                  "search_analyzer":"english"
               },
               "pic":{
                 "type":"text",
                 "index":false
               },
               "studymodel":{
                 "type":"text"
               }
        }
    }
    

    Text 文本类型

    1. analyzer

    通过analyzer属性指定分词器。

    上边指定了analyzer是指在索引和搜索都使用english,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性。

    1. index

    index属性指定是否索引。

    默认为index=true,即要进行索引,只有进行索引才可以从索引库搜索到。

    但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置为false。

    删除索引,重新创建映射,将pic的index设置为false,尝试根据pic去搜索,结果搜索不到数据。

    1. store

    是否在source之外存储,每个文档索引后会在 ES中保存一份原始文档,存放在"_source"中,一般情况下不需要设置store为true,因为在_source中已经有一份原始文档了。

    1. boost

    字段级别的分数加权,默认值是1.0

    1. doc_values

    对not_analyzed字段,默认都是开启,分词字段不能使用,对排序和聚合能提升较大性能,节约内存

    1. fielddata":{"format":"disabled"}

    针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value

    1. "fields":{"raw":{"type":"string","index":"not_analyzed"}}

    可以对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词

    1. "ignore_above":100

    超过100个字符的文本,将会被忽略,不被索引

    1. "include_in_all":ture

    设置是否此字段包含在_all字段中,默认是true,除非index设置成no选项

    "index_options":"docs"//4个可选参数docs(索引文档号) ,freqs(文档号+词频),positions(文档号+词频+位置,通常用来距离查询),offsets(文档号+词频+位置+偏移量,通常被使用在高亮字段)分词字段默认是position,其他的默认是docs

    "norms":{"enable":true,"loading":"lazy"}//分词字段默认配置,不分词字段:默认{"enable":false},存储长度因子和索引时boost,建议对需要参与评分字段使用 ,会额外增加内存消耗量

    "null_value":"NULL"//设置一些缺失字段的初始化值,只有string可以使用,分词字段的null值也会被分词

    "position_increament_gap":0//影响距离查询或近似查询,可以设置在多值字段的数据上火分词字段上,查询时可指定slop间隔,默认值是100

    "search_analyzer":"ik"//设置搜索时的分词器,默认跟ananlyzer是一致的,比如index时用standard+ngram,搜索时用standard用来完成自动提示功能

    "similarity":"BM25"//默认是TF/IDF算法,指定一个字段评分策略,仅仅对字符串型和分词类型有效

    "term_vector":"no"//默认不存储向量信息,支持参数yes(term存储),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用

    测试

    PUT book/_mapping
    {
            "properties": {
               "name": {
                      "type": "text"
                },
               "description": {
                  "type": "text",
                  "analyzer":"english",
                  "search_analyzer":"english"
               },
               "pic":{
                 "type":"text",
                 "index":false
               },
               "studymodel":{
                 "type":"text"
               }
        }
    }
    

    插入文档:

    PUT /book/_doc/1
    {
      "name":"Bootstrap开发框架",
      "description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。",
      "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
      "studymodel":"201002"
    }
    

    Get /book/_search?q=name:开发

    Get /book/_search?q=description:开发

    Get /book/_search?q=pic:group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg

    Get /book/_search?q=studymodel:201002

    通过测试发现:name和description都支持全文检索,pic不可作为查询条件。

    keyword关键字字段

    目前已经取代了"index": false。上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过虑、排序、聚合等。

    date日期类型

    日期类型不用设置分词器。

    通常日期类型的字段用于排序。

    通过format设置日期格式

    例子:

    下边的设置允许date字段存储年月日时分秒、年月日及毫秒三种格式。

    {
        "properties": {
            "timestamp": {
              "type":   "date",
              "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
            }
          }
    }
    

    插入文档:

    Post book/doc/3 
    {
    "name": "spring开发基础",
    "description": "spring 在java领域非常流行,java程序员都在用。",
    "studymodel": "201001",
     "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
     "timestamp":"2018-07-04 18:28:58"
    }
    

    数值类型

    下边是ES支持的数值类型

    1. 尽量选择范围小的类型,提高搜索效率

    2. 对于浮点数尽量用比例因子,比如一个价格字段,单位为元,我们将比例因子设置为100这在ES中会按 分 存储,映射如下:

    "price": {
            "type": "scaled_float",
            "scaling_factor": 100
      },
    

    由于比例因子为100,如果我们输入的价格是23.45则ES中会将23.45乘以100存储在ES中。

    如果输入的价格是23.456,ES会将23.456乘以100再取一个接近原始值的数,得出2346。

    使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。

    如果比例因子不适合,则从下表选择范围小的去用:

    更新已有映射,并插入文档:

    PUT book/doc/3
    {
    "name": "spring开发基础",
    "description": "spring 在java领域非常流行,java程序员都在用。",
    "studymodel": "201001",
     "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
     "timestamp":"2018-07-04 18:28:58",
     "price":38.6
    }
    

    修改映射

    只能创建index时手动建立mapping,或者新增field mapping,但是不能update field mapping。

    因为已有数据按照映射早已分词存储好。如果修改,那这些存量数据怎么办。

    新增一个字段mapping

    PUT /book/_mapping/
    {
      "properties" : {
        "new_field" : {
          "type" :    "text",
         "index":    "false"
        }
      }
    }
    

    如果修改mapping,会报错

    PUT /book/_mapping/
    {
      "properties" : {
        "studymodel" : {
         "type" :    "keyword"
        }
      }
    }
    

    返回:

    {
      "error": {
        "root_cause": [
          {
            "type": "illegal_argument_exception",
            "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]"
          }
        ],
        "type": "illegal_argument_exception",
        "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]"
      },
      "status": 400
    }
    

    删除映射

    通过删除索引来删除映射。

    1.8 定制dynamic mapping

    定制dynamic策略

    true:遇到陌生字段,就进行dynamic mapping

    false:新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍将出现在返回点击的源字段中。这些字段不会添加到映射中,必须显式添加新字段。

    strict:遇到陌生字段,就报错

    创建mapping

    PUT /my_index
    {
        "mappings": {
          "dynamic": "strict",
           "properties": {
            "title": {
              "type": "text"
            },
            "address": {
              "type": "object",
              "dynamic": "true"
            }
            }
        }
    }
    

    插入数据

    PUT /my_index/_doc/1
    {
      "title": "my article",
      "content": "this is my article",
      "address": {
        "province": "guangdong",
        "city": "guangzhou"
      }
    }
    

    报错

    {
      "error": {
        "root_cause": [
          {
            "type": "strict_dynamic_mapping_exception",
            "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
          }
        ],
        "type": "strict_dynamic_mapping_exception",
        "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
      },
      "status": 400
    }
    

    自定义 dynamic mapping策略

    es会根据传入的值,推断类型。

    date_detection 日期探测

    默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mapping成date,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型。

    PUT /my_index
    {
        "mappings": {
          "date_detection": false,
           "properties": {
            "title": {
              "type": "text"
            },
            "address": {
              "type": "object",
              "dynamic": "true"
            }
            }
        }
    }
    

    测试

    PUT /my_index/_doc/1
    {
      "title": "my article",
      "content": "this is my article",
      "address": {
        "province": "guangdong",
        "city": "guangzhou"
      },
      "post_date":"2019-09-10"
    }
    

    查看映射

    GET /my_index/_mapping
    

    自定义日期格式

    PUT my_index
    {
      "mappings": {
        "dynamic_date_formats": ["MM/dd/yyyy"]
      }
    }
    

    插入数据

    PUT my_index/_doc/1
    {
      "create_date": "09/25/2019"
    }
    

    numeric_detection 数字探测

    虽然json支持本机浮点和整数数据类型,但某些应用程序或语言有时可能将数字呈现为字符串。通常正确的解决方案是显式地映射这些字段,但是可以启用数字检测(默认情况下禁用)来自动完成这些操作。

    PUT my_index
    {
      "mappings": {
        "numeric_detection": true
      }
    }
    
    PUT my_index/_doc/1
    {
      "my_float":   "1.0", 
      "my_integer": "1" 
    }
    

    定制自己的dynamic mapping template

    PUT /my_index
    {
        "mappings": {
                "dynamic_templates": [
                    { 
                      "en": {
                          "match":              "*_en", 
                          "match_mapping_type": "string",
                          "mapping": {
                              "type":           "text",
                              "analyzer":       "english"
                          }
                    }                  
                }
            ]
        }
    }
    

    插入数据

    PUT /my_index/_doc/1
    {
      "title": "this is my first article"
    }
    
    PUT /my_index/_doc/2
    {
      "title_en": "this is my first article"
    }
    

    搜索

    GET my_index/_search?q=first
    GET my_index/_search?q=is
    

    title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的

    title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到了

    模板写法

    PUT my_index
    {
      "mappings": {
        "dynamic_templates": [
          {
            "integers": {
              "match_mapping_type": "long",
              "mapping": {
                "type": "integer"
              }
            }
          },
          {
            "strings": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "text",
                "fields": {
                  "raw": {
                    "type":  "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
        ]
      }
    }
    

    模板参数

    "match":   "long_*",
    "unmatch": "*_text",
    "match_mapping_type": "string",
    "path_match":   "name.*",
    "path_unmatch": "*.middle",
    
    "match_pattern": "regex",
    "match": "^profit_\d+$"
    

    场景

    1结构化搜索

    默认情况下,elasticsearch将字符串字段映射为带有子关键字字段的文本字段。但是,如果只对结构化内容进行索引,而对全文搜索不感兴趣,则可以仅将“字段”映射为“关键字”。请注意,这意味着为了搜索这些字段,必须搜索索引所用的完全相同的值。

        {
            "strings_as_keywords": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "keyword"
              }
            }
          }
    

    2仅搜索

    与前面的示例相反,如果您只关心字符串字段的全文搜索,并且不打算对字符串字段运行聚合、排序或精确搜索,您可以告诉弹性搜索将其仅映射为文本字段(这是5之前的默认行为)

        {
            "strings_as_text": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "text"
              }
            }
          }
    

    3norms 不关心评分

    norms是指标时间的评分因素。如果您不关心评分,例如,如果您从不按评分对文档进行排序,则可以在索引中禁用这些评分因子的存储并节省一些空间。

    {
            "strings_as_keywords": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "text",
                "norms": false,
                "fields": {
                  "keyword": {
                    "type": "keyword",
                    "ignore_above": 256
                  }
                }
              }
            }
          }
    

    1.9 复杂数据类型

    multivalue field

    { "tags": [ "tag1", "tag2" ]}

    建立索引时与string是一样的,数据类型不能混

    empty field

    null,[],[null]

    object field

    PUT /company/_doc/1
    {
      "address": {
        "country": "china",
        "province": "guangdong",
        "city": "guangzhou"
      },
      "name": "jack",
      "age": 27,
      "join_date": "2019-01-01"
    }
    

    address:object类型

    查询映射

    GET /company/_mapping
    {
      "company" : {
        "mappings" : {
          "properties" : {
            "address" : {
              "properties" : {
                "city" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                },
                "country" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                },
                "province" : {
                  "type" : "text",
                  "fields" : {
                    "keyword" : {
                      "type" : "keyword",
                      "ignore_above" : 256
                    }
                  }
                }
              }
            },
            "age" : {
              "type" : "long"
            },
            "join_date" : {
              "type" : "date"
            },
            "name" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
    

    object

    {
      "address": {
        "country": "china",
        "province": "guangdong",
        "city": "guangzhou"
      },
      "name": "jack",
      "age": 27,
      "join_date": "2017-01-01"
    }
    

    底层存储格式

    {
        "name":            [jack],
        "age":          [27],
        "join_date":      [2017-01-01],
        "address.country":         [china],
        "address.province":   [guangdong],
        "address.city":  [guangzhou]
    }
    

    对象数组:

    {
        "authors": [
            { "age": 26, "name": "Jack White"},
            { "age": 55, "name": "Tom Jones"},
            { "age": 39, "name": "Kitty Smith"}
        ]
    }
    

    存储格式:

    {
        "authors.age":    [26, 55, 39],
        "authors.name":   [jack, white, tom, jones, kitty, smith]
    }
    

    2.0 数据建模

    20180901144437612.png 20180901144234453.png

    以下的索引 Mapping中,_source设置为false,同时各个字段的store根据需求设置了true和false。
    url的doc_values设置为false,该字段url不用于聚合和排序操作。

    PUT blog_index
    {
      "mappings": {
        "doc": {
          "_source": {
            "enabled": false
          },
          "properties": {
            "title": {
              "type": "text",
              "fields": {
                "keyword": {
                  "type": "keyword",
                  "ignore_above": 100
                }
              },
              "store": true
            },
            "publish_date": {
              "type": "date",
              "store": true
            },
            "author": {
              "type": "keyword",
              "ignore_above": 100, 
              "store": true
            },
            "abstract": {
              "type": "text",
              "store": true
            },
            "content": {
              "type": "text",
              "store": true
            },
            "url": {
              "type": "keyword",
              "doc_values":false,
              "norms":false,
              "ignore_above": 100, 
              "store": true
            }
          }
        }
      }
    }
    

    ES多表关联

    目前ES主要有以下4种常用的方法来处理数据实体间的关联关系:

    (1)Application-side joins(服务端Join或客户端Join)
    这种方式,索引之间完全独立(利于对数据进行标准化处理,如便于上述两种增量同步的实现),由应用端的多次查询来实现近似关联关系查询。这种方法适用于第一个实体只有少量的文档记录的情况(使用ES的terms查询具有上限,默认1024,具体可在elasticsearch.yml中修改),并且最好它们很少改变。这将允许应用程序对结果进行缓存,并避免经常运行第一次查询。

    (2)Data denormalization(数据的非规范化)
    这种方式,通俗点就是通过字段冗余,以一张大宽表来实现粗粒度的index,这样可以充分发挥扁平化的优势。但是这是以牺牲索引性能及灵活度为代价的。使用的前提:冗余的字段应该是很少改变的;比较适合与一对少量关系的处理。当业务数据库并非采用非规范化设计时,这时要将数据同步到作为二级索引库的ES中,就很难使用上述增量同步方案,必须进行定制化开发,基于特定业务进行应用开发来处理join关联和实体拼接。

    ps:宽表处理在处理一对多、多对多关系时,会有字段冗余问题,适合“一对少量”且这个“一”更新不频繁的应用场景。宽表化处理,在查询阶段如果只需要“一”这部分时,需要进行结果去重处理(可以使用ES5.x的字段折叠特性,但无法准确获取分页总数,产品设计上需采用上拉加载分页方式)

    (3)Nested objects(嵌套文档)
    索引性能和查询性能二者不可兼得,必须进行取舍。嵌套文档将实体关系嵌套组合在单文档内部(类似与json的一对多层级结构),这种方式牺牲索引性能(文档内任一属性变化都需要重新索引该文档)来换取查询性能,可以同时返回关系实体,比较适合于一对少量的关系处理。
    ps: 当使用嵌套文档时,使用通用的查询方式是无法访问到的,必须使用合适的查询方式(nested query、nested filter、nested facet等),很多场景下,使用嵌套文档的复杂度在于索引阶段对关联关系的组织拼装。

    (4)Parent/child relationships(父子文档)
    父子文档牺牲了一定的查询性能来换取索引性能,适用于一对多的关系处理。其通过两种type的文档来表示父子实体,父子文档的索引是独立的。父-子文档ID映射存储在 Doc Values 中。当映射完全在内存中时, Doc Values 提供对映射的快速处理能力,另一方面当映射非常大时,可以通过溢出到磁盘提供足够的扩展能力。 在查询parent-child替代方案时,发现了一种filter-terms的语法,要求某一字段里有关联实体的ID列表。基本的原理是在terms的时候,对于多项取值,如果在另外的index或者type里已知主键id的情况下,某一字段有这些值,可以直接嵌套查询。具体可参考官方文档的示例:通过用户里的粉丝关系,微博和用户的关系,来查询某个用户的粉丝发表的微博列表。
    ps:父子文档相比嵌套文档较灵活,但只适用于“一对大量”且这个“一”不是海量的应用场景,该方式比较耗内存和CPU,这种方式查询比嵌套方式慢5~10倍,且需要使用特定的has_parent和has_child过滤器查询语法,查询结果不能同时返回父子文档(一次join查询只能返回一种类型的文档)。而受限于父子文档必须在同一分片上,ES父子文档在滚动索引、多索引场景下对父子关系存储和联合查询支持得不好,而且子文档type删除比较麻烦(子文档删除必须提供父文档ID)。

    如果业务端对查询性能要求很高的话,还是建议使用宽表化处理*的方式,这样也可以比较好地应对聚合的需求。在索引阶段需要做join处理,查询阶段可能需要做去重处理,分页方式可能也得权衡考虑下。

    对比 Nested Object Parent/Child
    优点 文档存储在一块,读取性能高 父子文档独立更新,互不影响
    缺点 更新父或子文档时需要更新整个文档 为了维护join的关系,需要占用内存,读取性能较差
    场景 子文档偶尔更新,查询频繁 子文档频繁更新

    相关文章

      网友评论

          本文标题:elasticsearch之六Mapping映射

          本文链接:https://www.haomeiwen.com/subject/fkfifhtx.html