MongoDB 笔记

Posted by banban on 2022-04-11

最佳实践

传送门:《50 Tips and Tricks for MongoDB Developers》

速度优先使用嵌入数据,完整性优先使用引用数据

规范化架构

{
     "_id" : productId,
     "name" : name,
     "price" : price,
     "desc" : description
}

{
    "_id" : orderId,
    "user" : userInfo,
    "items" : [
        productId1,
        productId2,
        productId3
    ]
}

非规范化架构

{
  "_id" : productId,
  "name" : name,
  "price" : price,
  "desc" : description
}

{
​    "_id" : orderId,
​    "user" : userInfo,
​    "items" : [
​        {
​            "_id" : productId1,
​            "name" : name1,
​            "price" : price1
​        },
​        {
​            "_id" : productId2,
​            "name" : name2,
​            "price" : price2
​        },
​        {
​            "_id" : productId3,
​            "name" : name3,
​            "price" : price3
​        }
​    ]
}

合理建立索引

数据示例

{
    "_id": ObjectId('xxx'),
    "threadId": 123,
    "data": ISODate("2022-04-13")
}

查询语句

db.posts.find({"threadId" : id}).sort({"date" : 1}).limit(20)

业务经常需要这种数据分页式查询,则可以建立索引

db.posts.createIndex({'threadId' : 1, 'date' : 1}, {'background': true})

尽可能预填充已知内容

例如某一条记录是用于记录一天内固定的6个小时的访问情况

{
​    "_id" : pageId,
​    "start" : time,
​    "visits" : {
​        "minutes" : [
​            [num0, num1, ..., num59],
​            [num0, num1, ..., num59],
​            [num0, num1, ..., num59],
​            [num0, num1, ..., num59],
​            [num0, num1, ..., num59],
​            [num0, num1, ..., num59]
​        ],
​        "hours" : [num0, ..., num5] 
​    }
}

则对于那些仍未发生的内容,可以用缺省值先填充

{
​    "_id" : pageId,
​    "start" : someTime,
​    "visits" : {
​        "minutes" : [
​            [0, 0, ..., 0],
​            [0, 0, ..., 0],
​            [0, 0, ..., 0],
​            [0, 0, ..., 0],
​            [0, 0, ..., 0],
​            [0, 0, ..., 0]
​        ],
​        "hours" : [0, 0, 0, 0, 0, 0]
​    }
}

MongoDB不需要为新内容寻找空间,它只是更新已经输入的值,这样会快很多。

例如,在小时开始时,程序可能会执行以下操作:

> db.pages.update({"_id" : pageId, "start" : thisHour}, 
... {"$inc" : {"visits.0.0" : 3}})

尽可能预聚合

例:提前把 total 值算好,MongoDB 是很笨重的数据库,对简单的检索效率很高,但是在数据量大的情况下做很复杂的聚合,性能会随着复杂度提升而降低。

> db.food.update(criteria, {"$inc" : {"apples" : 10, "oranges" : -2, "total" : 8}})
> db.food.findOne()
{
    "_id" : 123,
    "apples" : 20,
    "oranges" : 3,
    "total" : 23
}

MongoDB提供了以下Read Preference Mode:

  • primary:默认模式,一切读操作都路由到replica set的primary节点
  • primaryPreferred:正常情况下都是路由到primary节点,只有当primary节点不可用(failover)的时候,才路由到secondary节点。
  • secondary:一切读操作都路由到replica set的secondary节点
  • secondaryPreferred:正常情况下都是路由到secondary节点,只有当secondary节点不可用的时候,才路由到primary节点。
  • nearest:从延时最小的节点读取数据,不管是primary还是secondary。对于分布式应用且MongoDB是多数据中心部署,nearest能保证最好的data locality。

踩坑记录

配置足够多的 Mongos 实例

在一些业务下,会频繁请求后端并读取 Mongo 数据。对于这种业务切忌增加耗时的操作

def get_method():
    # 这边通过 Mongo 获取数据
    data = self.mongo_service.get()

    # 这边有一个耗时的数据处理,例如从别的系统获取数据
    self.combine_data_from_http(data)

    return data