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 过程一定要多加注意,避免掉到坑里。

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

  1. MongoDB中的一些坑( 2.4.10 版本)

    http://www.jb51.net/article/62654.htm 1.MongoDB 数据库级锁 MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大 ...

  2. mongodb中数据类型的坑

    在mongodb中,我们给每个文档插入数据的时候,mongodb自动会为我们插入的数据创建数据类型.由于mongodb是一个非结构化的数据存储系统,因此在文档中你可以随意插入不同类型的字段,这和MyS ...

  3. MongoDB中的MapReduce介绍与使用

    一.简介 在用MongoDB查询返回的数据量很大的情况下,做一些比较复杂的统计和聚合操作做花费的时间很长的时候,可以用MongoDB中的MapReduce进行实现 MapReduce是个非常灵活和强大 ...

  4. MongoDB中MapReduce介绍与使用

    一.简介 在用MongoDB查询返回的数据量很大的情况下,做一些比较复杂的统计和聚合操作做花费的时间很长的时候,可以用MongoDB中的MapReduce进行实现 MapReduce是个非常灵活和强大 ...

  5. MongoDB中的数据类型

    mongoDB中存储的数据单元被称作文档.文档的格式与JSON很类似,只不过由于JSON表达的数据类型范围太小(null,boolean,numeric,string和object),mongoDB对 ...

  6. 整理iOS9适配中出现的坑(图文)

    原文: http://www.cnblogs.com/dsxniubility/p/4821184.html 整理iOS9适配中出现的坑(图文)   本文主要是说一些iOS9适配中出现的坑,如果只是要 ...

  7. 浅析mongodb中group分组

    这篇文章主要介绍了浅析mongodb中group分组的实现方法及示例,非常的简单实用,有需要的小伙伴可以参考下. group做的聚合有些复杂.先选定分组所依据的键,此后MongoDB就会将集合依据选定 ...

  8. MongoDB中insert方法、update方法、save方法简单对比

    MongoDB中insert方法.update方法.save方法简单对比 1.update方法 该方法用于更新数据,是对文档中的数据进行更新,改变则更新,没改变则不变. 2.insert方法 该方法用 ...

  9. 使用aggregate在MongoDB中查找重复的数据记录

    我们知道,MongoDB属于文档型数据库,其存储的文档类型都是JSON对象.正是由于这一特性,我们在Node.js中会经常使用MongoDB进行数据的存取.但由于Node.js是异步执行的,这就导致我 ...

随机推荐

  1. 使用OpenGL进行Mandelbrot集的可视化

    Mandelbrot集是哪一集?? Mandelbrot集不是哪一集!! 啊不对-- Mandelbrot集是哪一集!! 好像也不对-- Mandelbrot集是数集!! 所以--他不是一集而是数集? ...

  2. apt-mirror 校验错误文件处理

    apt-mirror是一个用来将Debian或Ubuntu的软件源镜像到本地的工具,这个工具工作得非常好,不过有的时候由于网络问题,会有一些文件的校验是失败的,但apt-mirror并不能发现,等到最 ...

  3. nginx支持pathinfo模式

    很久不使用apache了,渐渐对apache感到陌生,因为朋友有个ZendFramework框架从apache移到nginx下,需要pathinfo模式支持.网上海搜于是开始搜索nginx+pathi ...

  4. SQLite 数据库安装与创建数据库

    嵌入式关系数据库 Ubuntu $ sudo apt-get install sqlite3 sqlite3-dev CentOS, or Fedora $ yum install SQLite3 s ...

  5. Cesium--气泡弹窗

    参考资料 首先感谢以下博主们的帮助,本人刚接触Cesium不久,无奈只能拾人牙慧了. 由于cesium没有自带的点击弹出气泡的功能,所以需要自己去开发一个这样的功能,网络上资源很多,看到基本思路都一致 ...

  6. javascript的defer和async(转载)

    http://ued.ctrip.com/blog/?p=3121 我们常用的javascript标签,有两个和性能.js文件下载执行相关的属性:defer和async defer的含义[摘自http ...

  7. PHP百分号转小数

    <?php $a = "20.544545%"; echo (float)$a/100; ?>

  8. CPU接口练习 (仅以此程序证明 某个同学真的有毒!有毒!!!)

    1创建接口 package lianxi; public interface ICpu { public boolean neiCun();//内存接口 } 2创建一个类 连接这个接口 package ...

  9. Python解释器是单线程应用 IO 密集型 计算密集型 GIL global interpreter lock

    [Python解释器是单线程应用] [任意时刻,仅执行一个线程] 尽管Python解释器中可以运行多个线程,但是在任意给定的时刻只有一个线程会被解释器执行. [GIL锁 保证同时只有一个线程运行] 对 ...

  10. 剑指Offer:把数组排成最小的数【45】

    剑指Offer:把数组排成最小的数[45] 题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如,输入数组是{3.32.321},则打印出来的这3 ...