mongodb 关系、引用、覆盖索引查询

本文介绍MongoDB中如何处理文档间的关系,包括嵌入式和引用式两种方式,并讲解了DBRefs的使用及覆盖索引查询的优势。

一、关系

  MongoDB 的关系表示多个文档之间在逻辑上的相互联系。文档间可以通过嵌入和引用来建立联系。MongoDB 中的关系可以是:1对1,1对多,多对1,多对多。

一个用户可以用多个地址,这是典型的一对多关系。

  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"
} 

  1、嵌入式关系

  使用嵌入式方法,可以把地址文档嵌入到用户的文档中

{
    "_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})

 

  这种数据结构的缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能。

 

  2、引用式方法

  这种方法类似于关系型数据库中的外键,将address的_id存到user文档中

  

{
   "_id":ObjectId("52ffc33cd85242f436000001"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin",
   "address_ids": [
      ObjectId("52ffc4a5d85242602e000000"),
      ObjectId("52ffc4a5d85242602e000001")
   ]
}

  我们可以读取这些用户地址的对象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。

  DBRef的形式:

{ $ref : , $id : , $db :  }

  其中$ref:集合名称,$id:引用的id,$db:数据库名称(可选)。

  以下实例中用户数据文档使用了 DBRef, 字段 address:

{
   "_id":ObjectId("53402597d852426020000002"),
   "address": {
   "$ref": "address_home",
   "$id": ObjectId("534009e4d852427820000002"),
   "$db": "w3cschoolcc"},
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin"
}

  address DBRef 字段指定了引用的地址文档是在 address_home 集合下的 w3cschoolcc 数据库,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"
}

 

三、覆盖索引查询

  覆盖查询是以下的查询:

  • 所有的查询字段是索引的一部分
  • 所有的查询返回字段在同一个索引中

  

  由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。因为索引存在于RAM中,从索引中获取数据比通过扫描文档读取数据要快得多。

  例:user集合:

{
   "_id": ObjectId("53402597d852426020000002"),
   "contact": "987654321",
   "dob": "01-01-1991",
   "gender": "M",
   "name": "Tom Benzamin",
   "user_name": "tombenzamin"
}

  创建联合索引,字段为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})

  如果所有索引字段是一个数组则不能使用覆盖索引查询,所有索引字段是一个子文档。

 

MongoDB 中,关联查询的性能表现受到数据模型设计、索引策略以及硬件配置等多方面因素的影响。由于 MongoDB 是一种非关系型文档数据库,它并不支持传统关系数据库中的 JOIN 操作,因此在进行关联查询时,通常需要通过嵌套文档、引用 ID 或者聚合管道来实现。 ### 关联查询的性能表现 1. **嵌套文档模型**:如果将关联数据直接嵌套在主文档中(例如将评论直接嵌入文章文档),查询时可以一次性获取所有相关数据,这种方式在读取性能上具有优势,但会增加文档的更新复杂度。如果嵌套文档过大,也可能影响性能。 2. **引用 ID 模型**:在该模型中,主文档中仅存储关联文档的 ID,实际查询时需要通过多次查询获取完整数据。这种方式在更新时较为高效,但查询时需要额外的 I/O 操作来获取关联数据,可能导致性能下降 [^3]。 3. **聚合管道**:使用 `$lookup` 阶段进行关联查询,虽然功能强大,但在大数据集上执行时可能效率较低,尤其是在未正确使用索引的情况下 [^5]。 ### 优化方法 1. **数据模型设计优化**: - 合理选择范式化反范式化策略。频繁查询但较少更新的数据适合反范式化,以减少关联查询的开销;而频繁更新的数据则适合范式化,以减少冗余数据带来的更新代价 [^1]。 - 如果关联数据访问频率较高,可以考虑将其嵌入主文档中,以减少数据库往返次数。 2. **索引优化**: - 为关联字段(如外键引用)创建索引,可以显著提升查找效率。 - 使用复合索引覆盖多个查询字段,尤其是在 `$lookup` 或多条件查询中 。 - 对于经常进行排序或聚合操作的字段,也应考虑建立索引。 3. **内存缓存优化**: - 确保 MongoDB 有足够的内存用于缓存热数据和索引,可以通过调整 `wiredTigerCacheSizeGB` 参数来优化内存使用 [^2]。 - 保证热数据集的大小小于服务器内存容量,以提升数据访问速度 [^4]。 4. **硬件存储优化**: - 使用 SSD 等高性能存储设备,以减少磁盘 I/O 延迟。 - 配置适当的写关注(`writeConcern`)和日志(`journal`)参数,以在性能数据持久性之间取得平衡 [^2]。 5. **查询优化**: - 避免不必要的字段投影,使用 `.projection()` 限制返回字段,减少数据传输量。 - 对于聚合查询,尽量使用索引支持的阶段,并避免全集合扫描。 ### 示例代码:使用 `$lookup` 进行关联查询 ```javascript db.books.aggregate([ { $lookup: { from: "authors", localField: "authorId", foreignField: "_id", as: "author" } } ]) ``` 上述聚合操作将 `books` 集合 `authors` 集合进行关联,结果中 `author` 字段将包含匹配的作者信息。为了提升性能,应确保 `authorId` 和 `_id` 字段上已创建索引。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值