MongoDB中的一些坑(最好不要用)
MongoDB是目前炙手可热的NoSQL文档型数据库,它提供的一些特性很棒:如自动failover机制,自动sharding,无模式schemaless,大部分情况下性能也很棒。但是薄荷在深入使用MongoDB过程中,遇到了不少问题,下面总结几个我们遇到的坑。特别申明:我们目前用的MongoDB版本是2.4.10,曾经升级到MongoDB2.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})
不合理使用嵌入embeddocument
坑爹指数:5星
embeddocument是MongoDB相比关系数据库差异明显的一个地方,可以在某一个document中嵌入其它子document,这样可以在父子document保持在单一collection中,检索修改比较方便。
比如薄荷的应用情景中有一个Groupdocument,用户申请加入Group建模为GroupRequestdocument,我们最初的时候使用embed方式把GroupRequest放置到Group中。Ruby代码如下所示(使用了MongoidORM):
classGroup includeMongoid::Document ... embeds_many:group_requests ... end
classGroupRequest includeMongoid::Document ... embedded_in:group ... end