摘要:在Elasticsearch這樣的分布式系統中執行類似SQL的join連接是代價是非常大的。然而,Elasticsearch卻給我們提供了基于水平擴展的兩種連接形式。一、join總述1、關系類比在關系...
在Elasticsearch這樣的分布式系統中執行類似SQL的join連接是代價是非常大的。然而,Elasticsearch卻給我們提供了基于水平擴展的兩種連接形式。
一、join總述
1、關系類比
在關系型數據庫中,以MySQL為例,我們經常用到join關鍵字對有關系的兩張或者多張表進行關聯查詢。但是當數據量達到一定量級時,查詢性能就是經常困擾的問題。
由于es可以做到數億量級的秒查(具體由分片數量決定),這時候把數據同步到es是我們可以使用解決方案之一。
那么不禁有疑問問了,由于業務場景的決定,之前必須關聯查詢的兩張表還能做到進行關聯嗎?
答案是可以的,es也提供了類似于關系型數據庫的關聯查詢,但是它又與關系型數據的關聯查詢有明顯的區別與限制。
2、使用限制
由于es屬于分布式文檔型數據庫,數據自然是存在于多個分片之上的。Join字段自然不能像關系型數據庫中的join使用。在es中為了保證良好的查詢性能,最佳的實踐是將數據模型設置為非規范化文檔,通過字段冗余構造寬表,即存儲在一個索引中。
(1)父子文檔(數據)必須存儲在同一index中
(2)父子文檔(數據)必須存儲在同一個分片中
(3)一個index中只能包含一個join字段,但是可以有多個關系
(4)同一個index中,一個父關系可以對應多個子關系,一個子關系只對應一個父關系
3、性能問題
當然執行了join查詢固然性能會受到一定程度的影響。對于帶 has_child / has_parent 而言,其查詢性能會隨著指向唯一父文檔的匹配子文檔的數量增加而降低。開篇第一句摘自es官網描述,從ES官方的描述來看join關聯查詢對性能的損耗是極大的。
不過,在筆者使用的過程中,在6個分片的前提下,且子表數據量在千萬量級的情況下,關聯查詢的耗時還是在秒內的,許多場景還是可以接受的。
建議我們在使用前,根據分片的多少和預估未來數據量的大小提前做好性能測試,防止以后數量達到一定程度時,性能有明顯下降,那個時候再改存儲方案得不償失。
二、Mapping
1、舉例說明
這里以優惠券活動與優惠券明細為例,在一個優惠券活動中可以發放幾千萬的優惠券,所以券活動與券明細是一對多的關系。
券活動表字段:
字段 說明
activity_id 活動ID
activity_name 活動名稱
券明細表字段:
字段 說明
coupon_id 券ID
coupon_amount 券面額
activity_id 外鍵-活動ID
2、mapping釋義
join類型的字段主要用來在同一個索引中構建父子關聯關系。通過relations定義一組父子關系,每個關系都包含一個父級關系名稱和一個或多個子級關系名稱activity_coupon_field是一個關聯字段,內部定義了一組join關系,該字段為自命名,type指定關聯關系是join,固定寫法,relations定義父子關系,activity父類型名稱,coupon子類型名稱,名稱均為自命名。
{ "mappings": { "properties": { "activity_coupon_field": { "type": "join", "relations": { "activity": "coupon" } }, "activity_id": { "type": "keyword" }, "activity_name": { "type": "keyword" }, "coupon_id": { "type": "long" }, "coupon_amount": { "type": "long" } } } }
三、插入數據
1、插入父文檔
在put父文檔數據的時候,我們通常按照某種規則指定文檔ID,方便子文檔數據變更時易于得到父文檔ID。比如這里我們用activity_id的值:activity_100來作為父id
PUT /coupon/_doc/activity_100 { "activity_id": 100, "activity_name": "年貨節5元促銷優惠券", "activity_coupon_field": { "name": "activity" } }
2、插入子文檔
上邊已經指定了父文檔ID,而子表中已經包含有activity_id,所以很容易得到父文檔ID。
put子文檔數據時候,必須指定父文檔ID,就是父文檔中的_id,這樣父子數據才建立了關聯關系。與此同時還要指定routing字段為父文檔ID,這樣保證了父子數據在同一分片上。
PUT /coupon/_doc/coupon_711235?routing=activity_id_100 { "coupon_id": 711235, "coupon_amount": "5", "activity_id": 100, "activity_coupon_field": { "name": "coupon", "parent": "activity_id_100" //父ID } }
四、關聯查詢
1、has_parent查詢(父查子)
根據父文檔條件字段查詢符合條件的子文檔數據
例如:查詢包含“年貨節”活動字樣,且已經被領取過的券
{ "query": { "bool": { "must": [{ "parent_type": "activity", "has_parent": { "query": { "bool": { "must": [{ "term": { "status": { "value": 1 } } }, { "wildcard": { "activity_name": { "wildcard": "*年貨節*" } } }] } } } }] } } }
2、has_child查詢(子查父)
根據子文檔條件字段符合條件的父文檔數據
例如:查詢coupon_id=711235在那個存在于哪個券活動中
{ "query": { "bool": { "must": [{ "has_child": { "type": "coupon", "query": { "bool": { "must": [{ "term": { "coupon_id": { "value": 711235 } } }] } } } }] } } }