個人專題目錄
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總結
往es里面直接插入數據,es會自動建立索引,同時建立對應的mapping。(dynamic mapping)
mapping中就自動定義了每個field的數據類型
不同的數據類型(比如說text和date),可能有的是exact value,有的是full text
exact value,在建立倒排索引的時候,分詞的時候,是將整個值一起作為一個關鍵詞建立到倒排索引中的;full text,會經歷各種各樣的處理,分詞,normaliztion(時態轉換,同義詞轉換,大小寫轉換),才會建立到倒排索引中。
同時,exact value和full text類型的field就決定了,在一個搜索過來的時候,對exact value field或者是full text field進行搜索的行為也是不一樣的,會跟建立倒排索引的行為保持一致;比如說exact value搜索的時候,就是直接按照整個值進行匹配,full text query string,也會進行分詞和normalization再去倒排索引中去搜索
可以用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 文本類型
- analyzer
通過analyzer屬性指定分詞器。
上邊指定了analyzer是指在索引和搜索都使用english,如果單獨想定義搜索時使用的分詞器則可以通過search_analyzer屬性。
- index
index屬性指定是否索引。
默認為index=true,即要進行索引,只有進行索引才可以從索引庫搜索到。
但是也有一些內容不需要索引,比如:商品圖片地址只被用來展示圖片,不進行搜索圖片,此時可以將index設置為false。
刪除索引,重新創建映射,將pic的index設置為false,嘗試根據pic去搜索,結果搜索不到數據。
- store
是否在source之外存儲,每個文檔索引后會在 ES中保存一份原始文檔,存放在"_source"中,一般情況下不需要設置store為true,因為在_source中已經有一份原始文檔了。
- boost
字段級別的分數加權,默認值是1.0
- doc_values
對not_analyzed字段,默認都是開啟,分詞字段不能使用,對排序和聚合能提升較大性能,節約內存
- fielddata":{"format":"disabled"}
針對分詞字段,參與排序或聚合時能提高性能,不分詞字段統一建議使用doc_value
- "fields":{"raw":{"type":"string","index":"not_analyzed"}}
可以對一個字段提供多種索引模式,同一個字段的值,一個分詞,一個不分詞
- "ignore_above":100
超過100個字符的文本,將會被忽略,不被索引
- "include_in_all":ture
設置是否此字段包含在_all字段中,默認是true,除非index設置成no選項
"index_options":"docs"http://4個可選參數docs(索引文檔號) ,freqs(文檔號+詞頻),positions(文檔號+詞頻+位置,通常用來距離查詢),offsets(文檔號+詞頻+位置+偏移量,通常被使用在高亮字段)分詞字段默認是position,其他的默認是docs
"norms":{"enable":true,"loading":"lazy"}//分詞字段默認配置,不分詞字段:默認{"enable":false},存儲長度因子和索引時boost,建議對需要參與評分字段使用 ,會額外增加內存消耗量
"null_value":"NULL"http://設置一些缺失字段的初始化值,只有string可以使用,分詞字段的null值也會被分詞
"position_increament_gap":0//影響距離查詢或近似查詢,可以設置在多值字段的數據上火分詞字段上,查詢時可指定slop間隔,默認值是100
"search_analyzer":"ik"http://設置搜索時的分詞器,默認跟ananlyzer是一致的,比如index時用standard+ngram,搜索時用standard用來完成自動提示功能
"similarity":"BM25"http://默認是TF/IDF算法,指定一個字段評分策略,僅僅對字符串型和分詞類型有效
"term_vector":"no"http://默認不存儲向量信息,支持參數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支持的數值類型
盡量選擇范圍小的類型,提高搜索效率
對于浮點數盡量用比例因子,比如一個價格字段,單位為元,我們將比例因子設置為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 數據建模
以下的索引 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的關系,需要占用內存,讀取性能較差 |
場景 | 子文檔偶爾更新,查詢頻繁 | 子文檔頻繁更新 |