视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
MongoDB中的范式与反范式
2020-11-09 13:10:26 责编:小采
文档


很多社交类的应用程序都需要链接人、内容、粉丝、好友,以及其他一些事物。对于这些高度关联的数据使用内嵌的形式还是引用的形式不容易权衡。这一节会介绍社交图谱数据相关的注意事项。通常,关注、好友或者收藏可以简化为一个发布、订阅系统:一个用户可以订阅另一个用户相关的通知。这样,有两个基本操作需要比较高效:如何保存订阅者,如何将一个事件通知给所有订阅者。

比较常见的订阅实现方式有三种。第一种方式是将内容生产者内嵌在订阅者文档中:

{
 "_id": ObjectId("..."),
 "username": "batman",
 "email": "batman@waynetech.com",
 "following": [
 ObjectId("..."),
 ObjectId("...")
 ]
}

现在,对于一个给定的用户文档,可以使用形如db.activities.find({"user": {"$in": user["following"]}})的方式查询该用户感兴趣的所有活动信息。但是,对于一条刚刚发布的活动信息,如果要找出对这条信息感兴趣的所有用户,就不得不查询所有用户的“following”字段了。

另一种方式是将订阅者内嵌到生产者文档中:

{
 "_id": ObjectId("..."),
 "username": "joker",
 "email": "joker@mailinator.com",
 "followers": [
 ObjectId("..."),
 ObjectId("..."),
 ObjectId("...")
 ]
}

当这个生产者新发布一条信息时,我们立即就可以知道需要给哪些用户发布通知。这样做的缺点时,如果需要找到一个用户关注的用户列表,就必须查询整个用户集合。这样方式的优缺点与第一种方式的优缺点恰好相反。

同时,这两种方式都存在另一个问题:它们会使用户文档变得越来越大,改变也越来越频繁。通常,“following”和“followers”字段甚至不需要返回:查询粉丝列表有多频繁?如果用户比较频繁地关注某些人或者对一些人取消关注,也会导致大量的碎片。因此,最后的方案对数据进一步范式化,将订阅信息保存在单独的集合中,以避免这些缺点。进行这种成都的范式化可能有点儿过了,但是对于经常发生变化而且不需要与文档其他字段一起返回的字段,这非常有用。对“followers”字段做这种范式化使有意义的。

用一个集合来保存发布者和订阅者的关系,其中的文档结构可能如下所示:

{
 "_id": ObjectId("..."), //被关注者的"_id"
 "followers": [
 ObjectId("..."),
 ObjectId("..."),
 ObjectId("...")
 ]
}

这样可以使用户文档比较精简,但是需要额外的查询才能得到粉丝列表。由于“followers”数组的大小经常会发生变化,所以可以在这个集合上启用“usePowerOf2Sizes”,以保证users集合尽可能小。如果将followers集合保存在另一个数据库中,也可以在不过多影响users集合的前提下对其进行压缩。

应对威尔惠顿效应

不管使用什么样的策略,内嵌字段只能在子文档或者引用数量不是特别大的情况下有效发挥作用。对于比较有名的用户,可能会导致用于保存粉丝列表的文档溢出。对于这种情况的一种解决方案使在必要时使用“连续的”文档。例如:

> db.users.find({"username": "wil"})
{
 "_id": ObjectId("..."),
 "username": "wil",
 "email": "wil@example.com",
 "tbc": [
 ObjectId("123"), // just for example
 ObjectId("456") // same as above
 ],
 "followers": [
 ObjectId("..."),
 ObjectId("..."),
 ObjectId("..."),
 ...
 ]
}
{
 "_id": ObjectId("123"),
 "followers": [
 ObjectId("..."),
 ObjectId("..."),
 ObjectId("..."),
 ...
 ]
}
{
 "_id": ObjectId("456"),
 "followers": [
 ObjectId("..."),
 ObjectId("..."),
 ObjectId("..."),
 ...
 ]
}

对于这种情况,需要在应用程序中添加从“tbc”(to be continued)数组中取数据的相关逻辑。

说点什么

No silver bullet.

下载本文
显示全文
专题