MongoDB 的关系表示多个文档之间的在逻辑上的相互关系。
文档可以通过嵌入和引用来建立关系。
MongoDB 中的关系可以是:
1:1(1对1)1:N(1对多)N:1(多对1)N:N(多对多)接下来我们来考虑下用户与用户地址的关系。
一个用户可以有多个地址,所以是一对多关系。
以下是 user 文档的简单结构:
{ "_id":ObjectId("52ffc33cd85242f436000001"), "name": "Tom Hanks", "contact": "987654321", "dob": "01-01-1991" }以下是 address 文档的简单结构:
{ "_id":ObjectId("52ffc4a5d85242602e000000"), "building": "22 A, Indiana Apt", "pincode": 123456, "city": "Los Angeles", "state": "California" }使用嵌入式方法,我们可以把用户地址嵌入到用户的文档中:
{ "_id":ObjectId("52ffc33cd85242f436000001"), "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin", "address": [ { "building": "22 A, Indiana Apt", "pincode": 123456, "city": "Los Angeles", "state": "California" }, { "building": "170 A, Acropolis Apt", "pincode": 456789, "city": "Chicago", "state": "Illinois" }] }以上数据保存在单一文档中,可以比较容易的获取和维护数据。你可以这样查询用户的地址:
>db.users.findOne({"name":"Tom Benzamin"},{"address":1})注意:以上查询中 db 和 users 表示数据库和集合。
这种数据结构的缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能。
引用式关系是设计数据库时经常用到的方法,这种方法把用户数据文档和用户地址数据文档分开,通过引用文档的 id 字段来建立关系。
{ "_id":ObjectId("52ffc33cd85242f436000001"), "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin", "address_ids": [ ObjectId("52ffc4a5d85242602e000000"), ObjectId("52ffc4a5d85242602e000001") ] }以上实例中,用户文档的 address_ids 字段包含用户地址的对象 id(ObjectId)数组。
我们可以读取这些用户地址的对象 id (ObjectId)来获取用户的详细地址信息。
这种方法需要两次查询,第一次查询用户地址的对象 id (ObjectId),第二次通过查询的 id 获取用户的详细地址信息。
>var result = db.users.findOne({"name":"Tom Benzamin"},{"address_ids":1}) >var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})MongoDB 引用有两种:
手动引用(Manual References)DBRefs考虑这样的一个场景,我们在不同的集合中 (address_home, address_office, address_mailing, 等)存储不同的地址(住址,办公室地址,邮件地址等)。
这样,我们在调用不同地址时,也需要指定集合,一个文档从多个集合引用文档,我们应该使用 DBRefs 。
DBRefs 的形式:
{ $ref : , $id : , $db : }三个字段表示的意义为:
$ ref:集合名称$id:引用的 id$db:数据库名称,可选参数以下实例中用户数据文档使用了 DBRef ,字段 address:
{ "_id":ObjectId("53402597d852426020000002"), "address": { "$ref": "address_home", "$id": ObjectId("534009e4d852427820000002"), "$db": "runoob"}, "contact": "987654321", "dob": "01-01-1991", "name": "Tom Benzamin" }address DBRef 字段指定了引用的地址文档是在 runoob 数据库下的 address_home 集合,id 为 534009e4d852427820000002。
以下代码中,我们通过指定 $ref 参数(address_home 集合)来查找集合找那个指定 id 的用户地址信息:
>var user = db.users.findOne({"name":"Tom Benzamin"}) >var dbRef = user.address >db[dbRef.$ref].findOne({"_id":(dbRef.$id)})以上实例返回了 address_home 集合中的地址数据:
{ "_id" : ObjectId("534009e4d852427820000002"), "building" : "22 A, Indiana Apt", "pincode" : 123456, "city" : "Los Angeles", "state" : "California" } >db[dbRef.$ref].findOne({"_id":(dbRef.$id)})在 MongoDB 4.0 版本是这样写的:
>db[dbRef.$ref].findOne({"_id":ObjectId(dbRef.$id)})官方的 MongoDB 的文档中说明,覆盖查询时以下的查询:
所有的查询字段是索引的一部分所有的查询返回字段在同一个索引中由于所有初选的查询中的字段是索引的一部分,MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。
因为索引存在于 RAM 中,从索引中获取数据比通过扫描文档读取数据要快的多。
为了测试覆盖索引查询,使用以下 users 集合:
{ "_id": ObjectId("53402597d852426020000002"), "contact": "987654321", "dob": "01-01-1991", "gender": "M", "name": "Tom Benzamin", "user_name": "tombenzamin" }我们在 users 集合中创建联合索引,字段为 gender 和 user_name:
>db.users.ensureIndex({gender:1,user_name:1})现在,该索引会覆盖以下查询:
>db.users.find({gender:"M"},{user_name:1,_id:0})也就是说,对于上述查询,MongoDB 的不会去数据库文件中查询。相反,他会从索引中提取数据,这是非常快速的数据查询。
由于我们的索引中不包括 _id 字段,_id 在查询中会默认返回,我们可以在 MongoDB 的查询结果集中排除它。
下面的实例没有排除 _id,查询就不会覆盖:
>db.users.find({gender:"M"},{user_name:1})最后,如果是以下查询,不能使用覆盖索引查询:
所有索引字段是一个数组所有索引字段是一个子文档