非關係型數據庫MongoDB聯合查詢解決方案

前言

        關係型數據庫的使用有一套數學理論支撐,數據庫表結構優化其實質就是優化範式。通常在關係型數據庫中使用的第三範式。然而,在非關係型數據庫中,使用第二範式更能夠發揮非關係型數據庫中的優勢。
        第二範式需要滿足兩點:一,屬性不重複,二,所有的屬性依賴於主屬性。因此第二範式允許將一次查詢的所有屬性都存在同一張表中。

        第三範式需要滿足兩點:一,滿足第二範式,二,屬性不依賴於其它非主屬性,非主屬性之間必須相互獨立,不存在其他的函數關係。例如,學校包含班級,班級包含學生,學生所學課程。如果學生的ID是主屬性,其餘屬性爲非主屬性,那麼該關係中存在着大量的非主屬性之間的依賴,因此在第三範式中,不允許將學校信息,班級信息,學校及課程信息存在一張表裏,必須拆分爲獨立的表,並建立表的關聯關係。


       

        如上圖所示,第三範式的表結構所存儲的數據會比第二範式少很多,避免了冗餘屬性值的存儲。在第三範式的表結構中,更容易對數據進行修改;在查詢方面,符合第三範式的查詢方式更是有嚴密的數學規範。

        然而,第二範式更像是第三範式的查詢結果。

        無論是關係型數據庫還是非關係型數據庫,在實際應用中,我們都是希望通過第三範式建立數據表,更合理的存儲數據;又希望能夠在查詢時,擁有第二範式的結構,一次性查詢總比聯合查詢快得多。

非關係型Mongo數據庫

       在實際應用場景中,大多數業務都具有強關係,因此使用非關係型數據庫也不可避免的使用第三範式建立數據結構。但是在使用聯合查詢時,對其性能還是有很多地方需要考究的。

       其中,非關係型數據MongoDB提供了聯合查詢的方式$lookup,而$lookup使用的是類似SQL嵌套連接的查詢。(雖然,對副表的外部建立索引能提高 lookup 性能;但是,lookup 對副表的任何字段的操作(Match, Group 等等)都不會使用索引,當不論是主表或者副表的數據量大時,其連接查詢非常的慢,遠不如關係型數據庫,期待MongoDB 官網的繼續優化吧)

// 方式一:簡單的左連接查詢,連接字段僅爲一個時使用。
db.getCollection("classCol").aggregate([

    {
        $lookup: {
            from: "studentCol",
            localField: "_id",
            foreignField: "classId",
            as: "result"
        }
    }
])

// 方式二:推薦的連接查詢,該方式比較靈活,pipeline裏面可以使用aggregate的pipeline操作,而且支持多個關鍵字連接。
// 但是需要注意的是,該方式僅在MongoDB 3.6+版本的數據庫使用。
// 參考 https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

db.getCollection("classCol").aggregate([
    {
        $lookup: {
            from: "studentCol",
            let: {
                alias_local_class_id: "$_id"
            },
            pipeline: [{
                $match: {
                    $expr: {
                        $and:[
                                {
                                    $eq: [
                                        "$schoolId",                 // schoolCol foreign id的字段
                                        "$$alias_local_class_id"   
                                    ]
                                }
                        ]
                    }
                }
            }],
            as: "alias_result_set"
        }
    }
])

       其返回的結果的形式是嵌套式的文檔(並非SQL中左連接的查詢結果,該查詢會返回所有的被查詢的主表數據,而副本中沒有數據時,嵌套文檔爲空)。MongoDB指定的默認返回集大小爲100M,若超過默認值,MongoDB將會拋出異常(細節參考MongoDB官網)。若需要大量查詢時,可指定返回集 allowDiskUse 爲 true, 以及設置 Cursor(在3.6+版本中使用aggregate將強制設置Cursor,除非指定返回explain)。這一組合很好的解決了大量查詢數據返回的問題。

       使用這allowDiskUse和 Cursor這一組合進行大量查詢時,MongoDB會將查詢的結果寫入磁盤,同時,返回Cursor(細節參考MongoDB官網)。該方式很好的做到了一次查詢,然後對數據流進行操作,大大優化了查詢的性能。

       語句如下:

// 參考1: https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/#example-aggregate-method-initial-batch-size
// 參考2:https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/#db.collection.aggregate

db.getCollection("classCol").aggregate([

    {
        $lookup: {
            .... 與上同,省略
        }
    }
], { allowDiskUse: true, cursor: { batchSize: 1000 } })    // 1000爲單次返回的結果條數。

// cursor: { batchSize: 0 }  batchSize是指定第一次返回的結果條數,指定爲0時,適用於快速返回結果和錯誤。

         Java程序中使用聯合查詢參考《 java-mongo複雜管道聚合aggregate的填坑之路(分頁、allowDiskUse、統計)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章