在傳統的數據庫裏面,對數據關係描述無外乎三種,一對一,一對多和多對多的關係,若是有關聯關係的數據,一般咱們在建表的時候會添加主外鍵來創建數據聯繫,而後在查詢或者統計時候經過join來還原或者補全數據,最終獲得咱們須要的結果數據,那麼轉化到ElasticSearch裏面,如何或者怎樣來處理這些帶有關係的數據。數據庫
咱們都知道ElasticSearch是一個NoSQL類型的數據庫,自己是弱化了對關係的處理,由於像lucene,es,solr這樣的全文檢索框架對性能要求都是比較高的,一旦出現join這樣的操做,性能會很是差,因此在使用搜索框架時,咱們應該避免把搜索引擎當作關係型數據庫用。json
固然,現實數據確定是有關係的,那麼在es裏面是如何處理和管理這些帶有關係的數據呢?api
你們都知道,es天生對json數據支持的很是完美,只要是標準的json結構的數據,不管多麼複雜,不管是嵌套多少層,都能存儲到es裏面,進而可以查詢和分析,檢索。在這種機制上,es處理和管理關係主要有三種方式:數組
這是es默認的機制,也就是咱們並無設置任何mapping,直接向es服務端插入一條複雜的json數據,也能成功插入,並能支持檢索,(能這樣操做是由於es默認用的是動態mapping,只要插入的是標準的json結構就會自動轉換,固然咱們也能控制mapping類型,es裏面有動態mapping和靜態maping,靜態mapping還分嚴格類型,弱類型,通常類型,在此再也不展開,有興趣的能夠從官網瞭解下)以下面一條數據:數據結構
{ "name" : "Zach", "car" : [ { "make" : "Saturn", "model" : "SL" }, { "make" : "Subaru", "model" : "Imprezza" } ] }
最終轉化成的存儲結構是下面這樣的:app
{ "name" : "Zach", "car.make" : ["Saturn", "Subaru"] "car.model" : ["SL", "Imprezza"] }
由於es的底層lucene是天生支持多值域的存儲,因此在上面看起來像數組的結構,其實在es裏面存儲的就是這個字段多值域。框架
而後檢索的時候.符號就能檢索相對應的內容。這樣的一條數據,其實已經包含了數據和關係,看起來像一對多的關係,一我的擁有多輛汽車。但實際上並不能算嚴格意義上的關係,由於lucene底層是扁平化存儲的,這樣以來多個汽車的數據實際都是存到一塊兒的混雜的,你沒辦法單獨獲取到這我的某一輛汽車的數據,由於整條數據都是一個總體,不管什麼操做整條數據都會返回。curl
在方案一里面,咱們指出了array存儲的數組對象,並非嚴格意義的關係,由於第二層的數據是沒有分離的,若是想要分離,就必須使用nested類型來顯式定義數據結構。只有這樣,第二層的多個汽車數據纔是獨立的互不影響,也就是說能夠單獨獲取或查詢某一輛汽車的數據。elasticsearch
一樣的json數據:ide
{ "name" : "Zach", "car" : [ { "make" : "Saturn", "model" : "SL" }, { "make" : "Subaru", "model" : "Imprezza" } ] }
在方案1裏面,最終到es裏面會存儲一條數據,在第二種類型裏面,而若是聲明瞭car類型是nested,那麼最終存儲到es的數量會顯示3,這裏解釋一下3是怎麼來的 = 1個root文檔+2個汽車文檔,nested聲明類型,每個實例都是一個新的document,因此在查詢的時候纔可以獨立進行查詢,而且性能還不錯,由於es底層會把整條數據存在同一個shard的lucene的sengment裏面,缺點是更新的代價比較大,每個子文檔的更新都要重建整個結構體的索引,因此nested適合不常常update的嵌套多級關係的場景。
nested類型的數據,須要用其指定的查詢和聚合方法才能生效,普通的es查詢只能查詢1級也就是root級的屬性,嵌套的屬性是不能查的,若是想要查,必須用嵌套查詢或者聚合才行。
嵌套應用有兩種模式:
第一種:嵌套查詢
每一個查詢都是單個文檔內生效,包括排序,
第二種:嵌套聚合或者過濾
對同一層級的全部文檔都是全局生效,包括過濾排序
parent/children 模式與nested很是相似,可是應用場景側重點有所不一樣。
在使用parent/children管理關聯關係時,es會在每一個shard的內存中維護一張關係表,在檢索時,經過has_parent和has_child過濾器來獲得關聯的數據,這種模式下父文檔與子文檔也是獨立的,查詢性能會比nested模式稍低,由於父文檔和子文檔在插入的時候會經過route使得他們都分佈在同一個shard裏面,但並不保證在同一個lucene的sengment索引段裏面,因此檢索性能稍低,除此以外,每次檢索es都須要從內存的關係表裏面獲得數據關聯的信息,也須要花費必定的時間,相比nested的優點在於,父文檔或者子文檔的更新,並不影響其餘的文檔,因此對於更新頻繁的多級關係,使用parent/children模式,最爲合適不過。
父文檔的mapping type:
{ "mappings":{ "person":{ "name":{ "type":"string" } } } }
子文檔的mapping type:
{ "homes":{ "_parent":{ "type" : "person" }, "state" : { "type" : "string" } } }
插入數據時,須要先插入父文檔:
curl -XPUT localhost:9200/test/person/zach/ -d' { "name" : "Zach" }
而後插入子文檔時,須要加上路由字段:
$ curl -XPOST localhost:9200/homes?parent=zach -d' { "state" : "Ohio" } $ curl -XPOST localhost:9200/test/homes?parent=zach -d' { "state" : "South Carolina" }
最終,父文檔zach就關聯上了兩個子文檔,在查詢時候能夠經過parent/children特定查詢來獲取數據。
總結:
方法一:
(1)簡單,快速,性能較高
(2)對維護一對一的關係比較擅長
(3)不須要特殊的查詢
方法二:
(1)因爲底層存儲在同一個lucene的sengment裏,因此讀取和查詢性能對比方法三更快
(2)更新單個子文檔,會重建整個數據結構,因此不適合更新頻繁的嵌套場景
(3)能夠維護一對多和多對多的存儲關係
方法三:
(1)多個關係數據,存儲徹底獨立,可是存在同一個shard裏面,因此讀取和查詢性能比方法二稍低
(2)須要額外的內存,維護管理關係列表
(3)更新文檔不影響其餘的子文檔,因此適合更新頻繁的場景
(4)排序和評分操做比較麻煩,須要額外的腳本函數支持
參考文檔:
https://www.elastic.co/blog/managing-relations-inside-elasticsearch