1羹令、前言
對于solr來說是無法做兩個collection之間的關(guān)聯(lián)的闷尿,es是否可以做到類似于表的join關(guān)聯(lián)那线椰,這就是本篇需要研究的內(nèi)容,
主要參考內(nèi)容是官方文檔纽窟。
先說下結(jié)論肖油,如果不做特殊處理,es是無法完成類似與表Join的關(guān)聯(lián)查詢的臂港。
2森枪、ES如何做關(guān)聯(lián)
官網(wǎng)里面有幾種支持關(guān)聯(lián)查詢的辦法:
2.1 應(yīng)用程序做關(guān)聯(lián)
這個沒有什么好說的,其實(shí)不算真正的關(guān)聯(lián)审孽,需要先查詢一個索引县袱,得到值構(gòu)造出條件再去查詢另外一個索引。
缺點(diǎn)也很明顯佑力,就是需要查詢多次式散。
2.2 冗余數(shù)據(jù)
簡單的來說就是將需要查詢的數(shù)據(jù)保存在一起,舉個例子如下:
假設(shè)有用戶索引和用戶發(fā)布博客的索引打颤,需要將用戶和博客信息關(guān)聯(lián)起來杂数,對于用戶來說,我們只需要取得用戶的姓名信息即可瘸洛,則可以這樣做:
可以看下map信息如下:
實(shí)際在es內(nèi),已經(jīng)將user下面的id和name進(jìn)行了扁平化處理次和,可以通過如下的方式查詢:
優(yōu)點(diǎn):查詢速度非撤蠢撸快,缺點(diǎn)是存在數(shù)據(jù)的冗余踏施。
2.3 嵌套對象
在ES中石蔗,對單個文檔的增刪改都是原子操作罕邀,有時候?yàn)榱朔奖阄覀儗?shí)體和它相關(guān)的明細(xì)是放在一個文檔中存儲的。比如論壇發(fā)的帖子和它的回復(fù)信息养距。
其實(shí)和冗余對象有點(diǎn)類似诉探,但是如果只是做查詢會發(fā)現(xiàn)有問題,因?yàn)閑s扁平處理之后:
{
"title": [ eggs, nest ],
"body": [ making, money, work, your ],
"tags": [ cash, shares ],
"comments.name": [ alice, john, smith, white ],
"comments.comment": [ article, great, like, more, please, this ],
"comments.age": [ 28, 31 ],
"comments.stars": [ 4, 5 ],
"comments.date": [ 2014-09-01, 2014-10-22 ]
}
tilte棍厌、body肾胯、tags被稱為父文檔或根文檔。
這樣數(shù)組內(nèi)之間是沒有順序關(guān)系的耘纱,這就導(dǎo)致了后面的查詢?nèi)匀豢梢圆榈綌?shù)據(jù)敬肚,嵌套對象是為了解決這個問題的,先看下普通的對象:
{
"query": {
"bool": {
"must": [
{ "match": { "comments.name": "Alice" }},
{ "match": { "comments.age": 28 }} ,
{ "match": { "comments.comment.keyword": "Great article" }}
]
}
}
}
嵌套對象,上面的例子是沒有定義map的情況直接發(fā)送數(shù)據(jù)束析,comments被定義為object艳馒,失去了數(shù)組內(nèi)的順序關(guān)系,如果先定義了nested對象员寇,則如下:
PUT my_index2
{
"mappings": {
"blogpost2": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"name": { "type": "text" },
"comment": { "type": "text" },
"age": { "type": "short" },
"stars": { "type": "short" },
"date": { "type": "date" }
}
}
}
}
}
}
再次發(fā)送相同的數(shù)據(jù):
PUT /my_index2/blogpost2/10
{
"title": "Nest eggs",
"body": "Making your money work...",
"tags": [ "cash", "shares" ],
"comments": [
{
"name": "John Smith",
"comment": "Great article",
"age": 28,
"stars": 4,
"date": "2014-09-01"
},
{
"name": "Alice White",
"comment": "More like this please",
"age": 31,
"stars": 5,
"date": "2014-10-22"
}
]
}
再次發(fā)起查詢:
為什么查不到弄慰,是因?yàn)閚ested對象有自己特定的語法如下:
{
"query": {
"nested": {
"path": "comments",
"score_mode": "max",
"query": {
"bool": {
"must": [
{
"term": {
"comments.age": 31
}
},
{
"term": {
"comments.name": "alice"
}
}
]
}
}
}
}
}
score_mode:表示嵌套文檔的最高得分納入到根文檔的計(jì)算之中。
嵌套模型的缺點(diǎn)如下:
當(dāng)對嵌套文檔做增加蝶锋、修改或者刪除時陆爽,整個文檔都要重新被索引。嵌套文檔越多牲览,這帶來的成本就越大墓陈。
查詢結(jié)果返回的是整個文檔,而不僅僅是匹配的嵌套文檔第献。盡管目前有計(jì)劃支持只返回根文檔中最佳匹配的嵌套文檔贡必,但目前還不支持。
2.4 父子對象
父子對象是最類似與表join的對象庸毫,父子關(guān)系的對象分別位于不同的文檔中仔拟,做到了很好的隔離。
有以下優(yōu)點(diǎn):
1)更新父文檔或子文檔時候飒赃,另一方不受影響利花。
2)創(chuàng)建和刪除子文檔,父文檔不受到影響载佳。
3)子文檔可以作為獨(dú)立的結(jié)果單獨(dú)返回炒事。
缺點(diǎn)是:
1)父文檔和子文檔必須存在同一個shard中。
2)貌似只能是同一個index的兩個type(對于es6.x版本只能支持一個type蔫慧,如何處理挠乳,目前還未看到)
原理:
Elasticsearch 維護(hù)了一個父文檔和子文檔的映射關(guān)系,得益于這個映射,父-子文檔關(guān)聯(lián)查詢操作非乘铮快盟蚣。
但是這個映射也對父-子文檔關(guān)系有個限制條件:父文檔和其所有子文檔,都必須要存儲在同一個分片中卖怜。
父-子文檔ID映射存儲在 Doc Values 中屎开。當(dāng)映射完全在內(nèi)存中時, Doc Values 提供對映射的快速處理能力马靠,
另一方面當(dāng)映射非常大時奄抽,可以通過溢出到磁盤提供足夠的擴(kuò)展能力
如何建立父子映射:
建立父-子文檔映射關(guān)系時只需要指定某一個文檔 type 是另一個文檔 type 的父親。 該關(guān)系可以在如下兩個時間點(diǎn)設(shè)置:
1)創(chuàng)建索引時虑粥;
2)在子文檔 type 創(chuàng)建之前更新父文檔的 mapping如孝。
舉例來說,對于公司和員工之間存在著類似的關(guān)系娩贷,即可以將公司信息看成員工信息的父文檔第晰。
如下:
PUT /company
{
"mappings": {
"branch": {}, //公司type
"employee": {
"_parent": {
"type": "branch" //employee 文檔 是 branch 文檔的子文檔。
}
}
}
}
父子文檔的創(chuàng)建
1)對于父對象來說彬祖,它是不知道有多少個子對象的茁瘦,所以按照一般的對象創(chuàng)建方法即可。
2)子對象創(chuàng)建方法:
PUT /company/employee/1?parent=london
{
"name": "Alice Smith",
"dob": "1970-10-24",
"hobby": "hiking"
}
父文檔 ID 有兩個作用:創(chuàng)建了父文檔和子文檔之間的關(guān)系储笑,并且保證了父文檔和子文檔都在同一個分片上甜熔。
這里面的父ID london 會作為路由的依據(jù),這樣子對象就會路由到父文檔同一個shard上突倍。
在執(zhí)行單文檔的請求時需要指定父文檔的 ID腔稀,單文檔請求包括:通過 GET 請求獲取一個子文檔;創(chuàng)建羽历、更新或刪除一個子文檔焊虏。
而執(zhí)行搜索請求時是不需要指定父文檔的ID,這是因?yàn)樗阉髡埱笫窍蛞粋€索引中的所有分片發(fā)起請求秕磷,而單文檔的操作是只會向存儲該文檔的分片發(fā)送請求诵闭。
因此,如果操作單個子文檔時不指定父文檔的 ID澎嚣,那么很有可能會把請求發(fā)送到錯誤的分片上疏尿。
父文檔的 ID 應(yīng)該在 bulk API 中指定
POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "london" }}
{ "name": "Mark Thomas", "dob": "1982-05-16", "hobby": "diving" }
{ "index": { "_id": 3, "parent": "liverpool" }}
{ "name": "Barry Smith", "dob": "1979-04-01", "hobby": "hiking" }
{ "index": { "_id": 4, "parent": "paris" }}
{ "name": "Adrien Grand", "dob": "1987-05-11", "hobby": "horses" }
通過子文檔查詢父文檔
查詢80后所在的公司信息:
GET /company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"range": {
"dob": {
"gte": "1980-01-01"
}
}
}
}
}
}
查詢至少兩個員工的公司:
GET /company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"min_children": 2,
"query": {
"match_all": {}
}
}
}
}
通過父文檔查詢子文檔
GET /company/employee/_search
{
"query": {
"has_parent": {
"type": "branch",
"query": {
"match": {
"country": "UK"
}
}
}
}
}
查詢公司在UK的所有員工。
每個子文檔都保存了父文檔的ID易桃。
當(dāng)你考慮父子關(guān)系是否適合你現(xiàn)有關(guān)系模型時褥琐,請考慮下面這些建議 :
- 盡量少地使用父子關(guān)系,僅在子文檔遠(yuǎn)多于父文檔時使用晤郑。
- 避免在一個查詢中使用多個父子聯(lián)合語句踩衩。
- 在 has_child 查詢中使用 filter 上下文嚼鹉,或者設(shè)置 score_mode 為 none 來避免計(jì)算文檔得分。
- 保證父 IDs 盡量短驱富,以便在 doc values 中更好地壓縮,被臨時載入時占用更少的內(nèi)存匹舞。
- 父子關(guān)聯(lián)查詢比普通的查詢慢5-10倍褐鸥。