知道这些MongoDB设计技巧,效率提升50%

本文探讨了MongoDB设计中的关键技巧,包括选择范式化还是反范式化设计,嵌入时间点数据的处理,避免不断增加的数据嵌入,预先分配空间的策略,以及如何有效使用索引和自增ID。同时,建议根据查询需求调整AND和OR查询的顺序,合理利用Repository和Converter提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

范式化设计还是反范式

考虑下这样的场景,我们的订单数据是这样的

商品:
{
  "_id": productId,
  "name": name,
  "price": price,
}

订单:
{
  "_id": orderId,
  "user": userId,
  "items": [
    productId1,
    productId2,
    productId3
  ]
}

当我们查询订单内容的时候,先通过orderId查询订单,然后在通过订单信息中的productId查询到对应的商品信息。这种设计下一次查询无法获取完整的订单。

范式化结果就是读取速度比较忙,当所有订单的一致性会有保证。

在来看看反范式化设计

订单:
{
  "_id": orderId,
  "user": userId,
  "items": [
   {
    "_id": productId1,
    "name": name,
    "price": price,
   },
   {
    "_id": productId2,
    "name": name,
    "price": price,
   },
  ]
}

这里将商品信息作为内嵌文档存在订单数据中,这样当显示的时候就只需要一次查询就可以了。

反范式读取速度快,一致性稍弱,商品信息的变更不能原子性地更新到多个文档。

那么我们一般使用哪一个呢?我们在设计的时候要考虑以下问题

  1. 读写比是怎样的?

可能读取了商品信息一万次才修改一次它的详细信息,为了那一次写入快一点或者保证一致性,搭上一万次的读取消耗值得吗?还有你认为引用的数据多久会更新一次?更新越少,越适合反范式化。有些极少变化的数据几乎根本不值得引用。比如名字,性别,地址等。

  1. 一致性重要吗?

如果是肯定的,则应该范式化。

  1. 要不要快速的读取?
    如果想要读取尽可能快,则要反范式化。在这个引用中就无所谓了,所以不能算考量因素,实时的应用要尽可能地反范式化。

订单文档非常适合反范式化,因为其中的商品信息不经常变化。就算变了也不必更新到所有订单。范式化再次就没有什么优势可言了。

所以本例中就是将订单反范式化。

嵌入时间点数据

当一个商品打折或者换了图片,并不需要更改原来的订单中的信息。类似这种特定于某一时刻的时间点数据,都应该做嵌入处理。

在我们上面提到的订单文档中有一处也是这样,地址就属于时间点数据。若某人更新了个人信息,那么并不需要改变其以往的订单内容。

千万不要嵌入不断增加的数据

MongoDB存储数据的机制决定了对数组不断追加数据是很低效的。在正常使用中数组和对象大小应该相对固定。

嵌入20,100,或者100000个子文档都不是问题,关键是提前这么做,之后基本保持不变。否则放任文档增长会使得系统慢的你受不了。

对于那些不断增加的内容,必须评论这个时候应该将其作为单独的文档处理比较合适。

尽可能预先分配空间

只要知道文档开始比较小,后来会变为确定的大小就可以使用这种优化方法,一开始插入文档的时候,就用和最终数据大小一样的垃圾数据填充,比如添加一个garbage字段(其中包含一个字符串,串大小与文档最终大小相同),然后马上重置字段

db.collection.insert({"_id" : 1,/* other fields */, "garbase": longString});
db.collection.update({"_id" : 1, });

这样,MongDB就会为文档今后的增长分配足够的空间

mongodb中存储文档是预留了空间的,允许文档扩容,但是当文档增大到一定地步的时候,就会超过原本分配的空间,此时文档就会进行移动

用数组存放要匿名访问的内嵌数据

一个常见的问题就是内嵌的信息到底是用数组还是用子文档存。如果确切知道要查询的内容,就要用子文档。如果有时候不太清楚查询的具体内容,就要用数组。当知道一些条目的查询条件时,通常该使用数组。

假设我想记录下游戏中某些物品的属性。我们可以这样建模

{
  "_id": 1,
  "items" : {

    "slingshot": {
      "type" : "weapon",
      "damage" : 30,
      "ranged" : true
    },

    "jar" : {
      "type": "container",
      "contains": "fairy"
    }

  }
}

假设要找出所有damage大于20的武器,子文档不支持这种查找方式,你只能知晓具体某种物品的信息才能查找,比如{“items.jar.damage”: {"$gt":20}}.
如果无需标识符,就要用数组

{
  "_id": 1,
  "items" : [

    {
      "id" : "slingshot"
      "type" : "weapon",
      "damage" : 30,
      "ranged" : true
    },

    {
      "id" : "jar",
      "type": "container",
      "contains": "fairy"
    }

  ]
}

比如{"items.damage": {"$gt":20}}就行了。如果还需要多条件查询,可以使用$elemMatch.

如何使用自增id代替ObjectId

有时候在使用过程中受限于业务或者其他情况,并不想使用ObjectId,而是想要使用自动Id来代替。但是MongoDB本身并没有提供这个功能,那么如何实现呢?

可以新建一个collection来保存自增id

{
    "_id" : ObjectId("59ed8d3df772d09a67eb25f6"),
    "fieldName" : "user",
    "seq" : NumberLong(100064)
}

fieldName表示哪个集合,那么下次要使用的时候只用取出这个值加1就可以了。代

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值