非关系型数据库MongoDB联合查询解决方案

前言

        关系型数据库的使用有一套数学理论支撑,数据库表结构优化其实质就是优化范式。通常在关系型数据库中使用的第三范式。然而,在非关系型数据库中,使用第二范式更能够发挥非关系型数据库中的优势。
        第二范式需要满足两点:一,属性不重复,二,所有的属性依赖于主属性。因此第二范式允许将一次查询的所有属性都存在同一张表中。

        第三范式需要满足两点:一,满足第二范式,二,属性不依赖于其它非主属性,非主属性之间必须相互独立,不存在其他的函数关系。例如,学校包含班级,班级包含学生,学生所学课程。如果学生的ID是主属性,其余属性为非主属性,那么该关系中存在着大量的非主属性之间的依赖,因此在第三范式中,不允许将学校信息,班级信息,学校及课程信息存在一张表里,必须拆分为独立的表,并建立表的关联关系。

非关系型数据库MongoDB联合查询解决方案_第1张图片
       

        如上图所示,第三范式的表结构所存储的数据会比第二范式少很多,避免了冗余属性值的存储。在第三范式的表结构中,更容易对数据进行修改;在查询方面,符合第三范式的查询方式更是有严密的数学规范。

        然而,第二范式更像是第三范式的查询结果。

        无论是关系型数据库还是非关系型数据库,在实际应用中,我们都是希望通过第三范式建立数据表,更合理的存储数据;又希望能够在查询时,拥有第二范式的结构,一次性查询总比联合查询快得多。

非关系型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、统计)》

 

 

你可能感兴趣的:(DB)