MongoDB中的一些坑(最好不要用)

MongoDB 是目前炙手可热的 NoSQL 文档型数据库,它提供的一些特性很棒:如自动 failover 机制,自动 sharding,无模式 schemaless,大部分情况下性能也很棒。但是薄荷在深入使用 MongoDB 过程中,遇到了不少问题,下面总结几个我们遇到的坑。特别申明:我们目前用的 MongoDB 版本是 2.4.10,曾经升级到 MongoDB 2.6.0 版本,问题依然存在,又回退到 2.4.10 版本。

MongoDB 数据库级锁

坑爹指数:5星(最高5星)

MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大的差异,InnoDB 和 Oracle 能提供行级粒度锁,而 MongoDB 只能提供 库级粒度锁,这意味着当 MongoDB 一个写锁处于占用状态时,其它的读写操作都得干等。

初看起来库级锁在大并发环境下有严重的问题,但是 MongoDB 依然能够保持大并发量和高性能,这是因为 MongoDB 的锁粒度虽然很粗放,但是在锁处理机制和关系数据库锁有很大差异,主要表现在:

MongoDB 没有完整事务支持,操作原子性只到单个 document 级别,所以通常操作粒度比较小;
MongoDB 锁实际占用时间是内存数据计算和变更时间,通常很快;
MongoDB 锁有一种临时放弃机制,当出现需要等待慢速 IO 读写数据时,可以先临时放弃,等 IO 完成之后再重新获取锁。
通常不出问题不等于没有问题,如果数据操作不当,依然会导致长时间占用写锁,比如下面提到的前台建索引操作,当出现这种情况的时候,整个数据库就处于完全阻塞状态,无法进行任何读写操作,情况十分严重。

解决问题的方法,尽量避免长时间占用写锁操作,如果有一些集合操作实在难以避免,可以考虑把这个集合放到一个单独的 MongoDB 库里,因为 MongoDB 不同库锁是相互隔离的,分离集合可以避免某一个集合操作引发全局阻塞问题。

建索引导致数据库阻塞

坑爹指数:3星

上面提到了 MongoDB 库级锁的问题,建索引就是一个容易引起长时间写锁的问题,MongoDB 在前台建索引时需要占用一个写锁(而且不会临时放弃),如果集合的数据量很大,建索引通常要花比较长时间,特别容易引起问题。

解决的方法很简单,MongoDB 提供了两种建索引的访问,一种是 background 方式,不需要长时间占用写锁,另一种是非 background 方式,需要长时间占用锁。使用 background 方式就可以解决问题。 例如,为超大表 posts 建立索引, 千万不用使用

复制代码代码如下:

db.posts.ensureIndex({user_id: 1})

而应该使用

复制代码代码如下:

db.posts.ensureIndex({user_id: 1}, {background: 1})

不合理使用嵌入 embed document

坑爹指数:5星

embed document 是 MongoDB 相比关系数据库差异明显的一个地方,可以在某一个 document 中嵌入其它子 document,这样可以在父子 document 保持在单一 collection 中,检索修改比较方便。

比如薄荷的应用情景中有一个 Group document,用户申请加入 Group 建模为 GroupRequest document,我们最初的时候使用 embed 方式把 GroupRequest 放置到 Group 中。 Ruby 代码如下所示(使用了 Mongoid ORM):

复制代码代码如下:

class Group
  include Mongoid::Document
  ...
  embeds_many :group_requests
  ...
end

class GroupRequest
  include Mongoid::Document
  ...
  embedded_in :group
  ...
end

这个使用方式让我们掉到坑里了,差点就爬不出来,它导致有接近两周的时间系统问题,高峰时段常有几分钟的系统卡顿,最严重一次甚至引起 MongoDB 宕机。

仔细分析后,发现某些活跃的 Group 的 group_requests 增加(当有新申请时)和更改(当通过或拒绝用户申请时)异常频繁,而这些操作经常长时间占用写锁,导致整个数据库阻塞。原因是当有增加 group_request 操作时,Group 预分配的空间不够,需要重新分配空间(内存和硬盘都需要),耗时较长,另外 Group 上建的索引很多,移动 Group 位置导致大量索引更新操作也很耗时,综合起来引起了长时间占用锁问题。

解决问题的方法,说起来也简单,就是把 embed 关联更改成的普通外键关联,就是类似关系数据库的做法,这样 group_request 增加或修改都只发生在 GroupRequest 上,简单快速,避免长时间占用写锁问题。当关联对象的数据不固定或者经常发生变化时,一定要避免使用 embed 关联,不然会死的很惨。

不合理使用 Array 字段

坑爹指数:4星

MongoDB 的 Array 字段是比较独特的一个特性,它可以在单个 document 里存储一些简单的一对多关系。

薄荷有一个应用情景使用遇到严重的性能问题,直接上代码如下所示:

复制代码代码如下:

class User
  include Mongoid::Document
  ...
  field :follower_user_ids, type: Array, default: []
  ...
end

User 中通过一个 Array 类型字段 follower_user_ids 保存用户关注的人的 id,用户关注的人从 10个到 3000 个不等,变化是比较频繁的,和上面 embed 引发的问题类似,频繁的 follower_user_ids 增加修改操作导致大量长时间数据库写锁,从而引发 MongoDB 数据库性能急剧下降。

解决问题的方法:我们把 follower_user_ids 转移到了内存数据库 redis 中,避免了频繁更改 MongoDB 中的 User, 从而彻底解决问题。如果不使用 redis,也可以建立一个 UserFollower 集合,使用外键形式关联。

先列举上面几个坑吧,都是害人不浅的陷阱,使用 MongoDB 过程一定要多加注意,避免掉到坑里。

原文地址:https://www.cnblogs.com/ExMan/p/9551330.html

时间: 2024-10-15 04:54:30

MongoDB中的一些坑(最好不要用)的相关文章

论mongo-connector如何将MongoDB中的json数组和嵌套对象更新至Solr引擎

有一些东西记录下来,不久之后就会再次用到. 1. 使复杂的嵌套对象从MongoDB更新到Solr 最近使用mongo-connector将MongoDB与Solr打通,对于简单的json,更新是没问题了.这次我们遇到的问题是,如果json中某一个field的值是数组类型,或者有嵌套了一个对象,那该怎么整.比如,我们在MongoDB中插入这么个货: { "_id": "555df36ec6cd08ea807a4324", "name": "

Netty中的那些坑

Netty中的那些坑(上篇) 最近开发了一个纯异步的redis客户端,算是比较深入的使用了一把netty.在使用过程中一边优化,一边解决各种坑.儿这些坑大部分基本上是Netty4对Netty3的改进部分引起的. 注:这里说的坑不是说netty不好,只是如果这些地方不注意,或者不去看netty的代码,就有可能掉进去了. 坑1: Netty 4的线程模型转变 在Netty 3的时候,upstream是在IO线程里执行的,而downstream是在业务线程里执行的.比如netty从网络读取一个包传递给

iOS内购(IAP)中的那些坑

公司的公共库原来并没有这部分的代码,以前做内购是用两个比较有名的github上的第三方库.一个叫MKStoreKit,另一个叫IAPManager,我看了一下写的都很辣鸡,使用起来很不方便,而且写的还不对...... 于是我自己写了一个,一开始写的也不是很好,受了上面两个垃圾库的影响(这两个库接口是用postNotification的),使用时还要监听事件,下面的小弟吐槽说不太好用.于是我又重做了一个接口为block的版本,感觉写的还是不错的.这下用的就很舒服了! 虽然github上也有几个写的

MongoDB中常用的find

接着前一篇文章,下面主要介绍一下MongoDB中常用的find操作. 先打开MongoDB shell,通过下面一组命令插入一些数据. 1 post1 = {"title":"learn MongoDB", "author":"Wilber", "date":new Date(), "score":90} 2 post2 = {"title":"learn

记一次Nancy 框架中遇到的坑

记一次Nancy 框架中遇到的坑 前几天,公司一个项目运行很久的Nancy框架的网站,遇到了一个很诡异的问题.Session 对象跳转到另外一个页面的时候,session对象被清空了,导致用户登录不上.搞了两天,终于解决了.于是把它记录下来,以避免其他人也碰到这种情况. 起因 公司一个Nancy 框架 开发的web网站,以前一直都是好的,nancy 框架也用在了很多项目里面,基本上已经很成熟很稳定了,但是在前几天发布某个web网站的时候,竟然出现部分用户登录不上的情况.刚开始以为是人员的权限有问

Python数组创建中的这些坑,你造吗?

本文和大家分享的主要是python 中创建数组过程中的一些坑,一起来看看吧,希望对大家 学习python 有所帮助. 1.问题的引出 在做leetcode 的题目时,遇到了一个求数组中出现次数最多的 k 个元素的题,参照答案的思路,首先利用一个 dict 记录所有元素出现的次数, key:value 中的 key 表示元素, value 表示元素出现的次数,随后根据元素出现的次数将元素放入对应的桶中,桶是一个二维数组,桶中第一个元素保存出现次数为 0 的元素,桶中第二个元素保存出现次数为 1 的

golang解析mongodb中的ISODate类型

在golang中可以使用time.Time数据类型来保存mongodb中的ISODate时间. g type Model struct {     uploadDate time.Time `bson:"uploadDate"` } m := Model{} if err := c.Find(nil).Select({"_id": 0, "uploadDate": 1}).One(&m); err != nil {     fmt.Pri

MongoDB中的聚合操作

根据MongoDB的文档描述,在MongoDB的聚合操作中,有以下五个聚合命令. 其中,count.distinct和group会提供很基本的功能,至于其他的高级聚合功能(sum.average.max.min),就需要通过mapReduce来实现了. 在MongoDB2.2版本以后,引入了新的聚合框架(聚合管道,aggregation pipeline ,使用aggregate命令),是一种基于管道概念的数据聚合操作. Name Description count Counts the num

Python获取个人网站的所有课程下载链接和密码,并保存到Mongodb中

1.获取网站课程的分类地址: ''' 爬取屌丝首页,获取每个分类名称和链接 ''' import requests from lxml import etree headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36', } def get_class_data(): list_dat