MongoDB 关系
MongoDB 的关系表示多个文档之间在逻辑上的相互联系。
文档间可以通过嵌入和引用来建立联系。
MongoDB 中的关系可以是:
- 1:1 (1对1)
- 1: N (1对多)
- N: 1 (多对1)
- N: N (多对多)
例:考虑下用户与用户地址的关系,一个用户有多个地址
# 地址集合
> db.address.find().pretty()
{
"_id" : ObjectId("5be128d356bc5be2ecaa4a38"),
"city" : "Beijing",
"area" : "ChaoYang"
}
{
"_id" : ObjectId("5be128ed56bc5be2ecaa4a39"),
"city" : "ShangHai",
"area" : "PuDong"
}
{
"_id" : ObjectId("5be1290756bc5be2ecaa4a3a"),
"city" : "GuangZhou",
"area" : "TianHe"
}
# 用户集合
> db.user.find().pretty()
{
"_id" : ObjectId("5be1297456bc5be2ecaa4a3b"),
"name" : "Tom",
"age" : 24,
"address_ids" : [
ObjectId("5be128d356bc5be2ecaa4a38"),
ObjectId("5be128ed56bc5be2ecaa4a39")
]
}
(1)嵌入式关系
使用嵌入式方法,我们可以把用户地址嵌入到用户的文档中:
{
"_id" : ObjectId("5be1297456bc5be2ecaa4a3b"),
"name" : "Tom",
"age" : 24,
"address" : [
{
"_id" : ObjectId("5be128d356bc5be2ecaa4a38"),
"city" : "Beijing",
"area" : "ChaoYang"
},
{
"_id" : ObjectId("5be128ed56bc5be2ecaa4a39"),
"city" : "ShangHai",
"area" : "PuDong"
}]
}
以上数据保存在单一的文档中,可以比较容易的获取和维护数据。 你可以这样查询用户的地址:
注意:以上查询中 db 和 users 表示数据库和集合。
db.user.findOne({"name":"Tom"},{"address":1})
这种数据结构的缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能。
(2)引用式关系
引用式关系是设计数据库时经常用到的方法,这种方法把用户数据文档和用户地址数据文档分开,通过引用文档的 id 字段来建立关系。
> db.user.insert({
... name: 'Tom',
... age: 24,
... address_ids: [
... ObjectId("5be128d356bc5be2ecaa4a38"),
... ObjectId("5be128ed56bc5be2ecaa4a39")
... ]
... })
以上实例中,用户文档的 address_ids 字段包含用户地址的对象id(ObjectId)数组。
我们可以读取这些用户地址的对象id(ObjectId)来获取用户的详细地址信息。
这种方法需要两次查询,第一次查询用户地址的对象id(ObjectId),第二次通过查询的id获取用户的详细地址信息。
> var result = db.user.findOne({'name': 'Tom'})
> result
{
"_id" : ObjectId("5be1297456bc5be2ecaa4a3b"),
"name" : "Tom",
"age" : 24,
"address_ids" : [
ObjectId("5be128d356bc5be2ecaa4a38"),
ObjectId("5be128ed56bc5be2ecaa4a39")
]
}
> var address = db.address.find({'_id': {'$in': result['address_ids']}})
> address
{ "_id" : ObjectId("5be128d356bc5be2ecaa4a38"), "city" : "Beijing", "area
aoYang" }
{ "_id" : ObjectId("5be128ed56bc5be2ecaa4a39"), "city" : "ShangHai", "are
uDong" }
MongoDB 数据库引用
MongoDB 引用有两种:
- 手动引用(Manual References)
- DBRefs
(1)DBRefs vs 手动引用
考虑这样的一个场景,我们在不同的集合中 (address_home, address_office, address_mailing, 等)存储不同的地址(住址,办公室地址,邮件地址等)。
这样,我们在调用不同地址时,也需要指定集合,一个文档从多个集合引用文档,我们应该使用 DBRefs。
(2)使用 DBRefs
DBRef的形式:
{ "$ref" : collection_name, "$id" : document_id, "$db" : dbName }
三个字段表示的意义为:
- $ref:集合名称
- $id:引用的id
- $db:数据库名称,可选参数
例:
# 集合 address_mail 中的数据
> db.address_mail.find().pretty()
{
"_id" : ObjectId("5be1316356bc5be2ecaa4a3c"),
"type" : "QQ",
"address" : "2315@qq.com"
}
{
"_id" : ObjectId("5be1319056bc5be2ecaa4a3d"),
"type" : "@163",
"address" : "18865500277@163.com"
}
{
"_id" : ObjectId("5be131ca56bc5be2ecaa4a3e"),
"type" : "@sina",
"address" : "zhy0815@sina.com"
}
# 用户数据文档使用了 DBRef, 字段 address
> db.user.insert({
... name: 'Michael',
... age: 30,
... address: {
... '$ref': 'address_mail', # 集合 address_mail
... '$id': ObjectId("5be1316356bc5be2ecaa4a3c"), # 集合 address_mail 的 "_id"
... }
... })
address DBRef 字段指定了引用的地址文档是在 address_mail 集合,id 为 5be1316356bc5be2ecaa4a3c。
以下代码中,我们通过指定 $ref 参数(address_home 集合)来查找集合中指定id的用户地址信息:
# 用户 'Michael' 的文档记录
> var user = db.user.findOne({'name': 'Michael'})
> user
{
"_id" : ObjectId("5be1328056bc5be2ecaa4a3f"),
"name" : "Michael",
"age" : 30,
"address" : DBRef("address_mail", ObjectId("5be1316356bc5be2ecaa4
}
> var dbRef = user.address
> dbRef
DBRef("address_mail", ObjectId("5be1316356bc5be2ecaa4a3c"))
> db[dbRef.$ref].findOne({'_id': (dbRef.$id)})
{ #返回了 address_mail 集合中的地址数据:
"_id" : ObjectId("5be1316356bc5be2ecaa4a3c"),
"type" : "QQ",
"address" : "2315@qq.com"
}
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})
最后,如果是以下的查询,不能使用覆盖索引查询:
- 所有索引字段是一个数组
- 所有索引字段是一个子文档