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):

1
2
3
4
5
6
7
8
9
10
11
12
13
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 里存储一些简单的一对多关系。

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

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

参考资料:
* 1.MongoDB 锁机制详解
* 2.MongoDB 建立索引操作文档

来自:http://xiewenwei.github.io/

MongoDB 那些坑(转)的更多相关文章

  1. 公网访问阿里云数据库MongoDB——填坑日记

    业务情景 两台服务器,一台阿里云ECS云服务器(专用网络),另一台是阿里云数据库MongoDB,处于安全考虑MongoDB是不运行外网连接的,那接下来就看怎么实现公网访问. 看到上面红色的网络类型描述 ...

  2. MongoDB 那些坑

    MongoDB 是目前炙手可热的 NoSQL 文档型数据库,它提供的一些特性很棒:如自动 failover 机制,自动 sharding,无模式 schemaless,大部分情况下性能也很棒.但是薄荷 ...

  3. mongodb 踩坑记录

    Map-Reduce Map-Reduce 是 mongodb 处理批量数据的大杀器,凡是数据量大并且定时处理能满足需求的,都可以试着扔给 mongodb,让它去 Map-Reduce. 以下截取自文 ...

  4. 【运维相关】MongoDB那些坑

    前言 某个早期技术债务项目线上有一个mongoDB服务,用途很简单,存一些文件而已.不过用户那边突然报个bug说上边的图片丢失了,起初没当回事认为是代码遇到错了,直到后来看了用户详细的bug复现后,登 ...

  5. mongodb 入坑

    一.安装mongodb https://www.mongodb.com/ 官网下载合适的版本,安装在C或者D盘,我选择的是默认路径C:\Program Files\MongoDB\Server\3.4 ...

  6. MongoDB记录(坑在末尾)

    Mongo数据库基本配置 基本配置 密码配置 pymongo认证 参考资料 基本配置 基本配置包括 1.端口号:默认27017,安全性较低 2.数据库文件位置 3.日志文件位置 4.日志写入模式 5. ...

  7. 【原创】MongoDB安装配置详解(标注两个坑)

    1.下载安装 3.4正式版([坑]不要最新版,有可能进度卡在这个位置不动,等了半个小时也没什么反映,) http://downloads.mongodb.org/win32/mongodb-win32 ...

  8. node+express+mongodb初体验

    从去年11月份到现在,一直想去学习nodejs,在这段时间体验了gulp.grunt.yeomen,fis,但是对于nodejs深入的去学习,去开发项目总是断断续续. 今天花了一天的时间,去了解整理整 ...

  9. (转)Ubuntu 17.04_64上搭建巡风扫描系统(资产信息漏洞扫描内网神器)

    巡风简介 巡风是一款适用于企业内网的漏洞快速应急.巡航扫描系统,通过搜索功能可清晰的了解内部网络资产分布情况,并且可指定漏洞插件对搜索结果进行快速漏洞检测并输出结果报表.其主体分为两部分:网络资产识别 ...

随机推荐

  1. python中的__call__

    如果python中的一个类定义了 __call__ 方法,那么这个类它的实例就可以作为函数调用,也就是实现了 () 运算符,即可调用对象协议 下面是一个简单的例子: class TmpTest: de ...

  2. 数链剖分(树的统计Count )

    题目链接:https://cn.vjudge.net/contest/279350#problem/C 具体思路:单点更新,区间查询,查询的时候有两种操作,查询区间最大值和区间和. 注意点:在查询的时 ...

  3. rap 部署

    Rap 安装 war包下载地址 https://github.com/thx/RAP/releases 创建数据库,并创建权限用户 mysql> create database rap_db c ...

  4. Linux中断(interrupt)子系统之四:驱动程序接口层 & 中断通用逻辑层【转】

    转自:http://blog.csdn.net/droidphone/article/details/7497787 在本系列文章的第一篇:Linux中断(interrupt)子系统之一:中断系统基本 ...

  5. find查找时排除目录及文件

    查找根目录下大于500M的文件,排除/proc目录 find / ! -path "/proc/*" -type f -size +500M | sort -rh|xargs ls ...

  6. casper Dom的操作

    phantom.casperTest = true; phantom.outputEncoding="utf-8"; var casper = require('casper'). ...

  7. js input输入框的总结

    一.输入框只能输入数字 原文:https://www.cnblogs.com/sese/p/5872144.html 分享下js限制输入框中只能输入数字的方法,包括整数与小数,分享几个例子,有需要的朋 ...

  8. 启动虚拟机报错VMware Workstation cannot connect to the virtual machine

    启动虚拟机报错: VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run ...

  9. 为你的 Hadoop 集群选择合适的硬件

    随着Apache Hadoop的起步,云客户的增多面临的首要问题就是如何为他们新的的Hadoop集群选择合适的硬件. 尽管Hadoop被设计为运行在行业标准的硬件上,提出一个理想的集群配置不想提供硬件 ...

  10. java 通过异常处理错误

    java的基本理念是"结构不佳的代码不能够运行" 一.概念 发现错误的理想时机是编译阶段,然而,编译期间并不能找出所有的错误,余下的问题必须在运行时期解决. 二.基本异常 异常情形 ...