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 ... endclass 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 过程一定要多加注意,避免掉到坑里。
本文向大家介绍Python中一些深不见底的“坑”,包括了Python中一些深不见底的“坑”的使用技巧和注意事项,需要的朋友参考一下 Python是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的“坑”里,下面,我就来总结一些Python里常见的坑。 列表创建和引用 嵌套列表的创建 使用*号来创建一个嵌套的list: 通过这个方法,可以得到一个包含3个list的嵌套list,
本文向大家介绍关于python中remove的一些坑小结,包括了关于python中remove的一些坑小结的使用技巧和注意事项,需要的朋友参考一下 前几天,使用python时遇到这么一个需求,删除一个列表中值为1的元素。我寻思着使用remove方法,但是remove方法只会删除第一个,于是我使用for循环去删除。代码和运行结果如下: 当时这个结果让我很懵逼,为什么1没有被删除完?查了资料发现,是f
最近一直在准备面试,看了不少招聘贴,也面了一些公司,总结一下其中谎言之外的陷阱。 薪资范围 这个长谈啦。如果你不擅长谈工资,那么这个薪资范畴的上限就没有意义,至今只看到 一家公司是不跟你谈工资,而是由他们的工程师给你评定,个人还是蛮喜欢这样的设定的。 更有甚者,连薪资下限都会跟你讨价还价,并且能够振振其辞!我的经验是,如果一家公司 在工资上都这么抠门的话,也就别指望它还能提供什么其它附加值了。 薪
本文向大家介绍升级到MySQL5.7后开发不得不注意的一些坑,包括了升级到MySQL5.7后开发不得不注意的一些坑的使用技巧和注意事项,需要的朋友参考一下 前言 前段时间,将线上MySQL数据库升级到了5.7。考虑到可能产生的不兼容性,在升级之前,确实也是战战兢兢,虽然测试环境,开发环境早在半年前就已提前升级。 基于前期的调研和朋友的反馈,与开发相关的主要有两点: sql_mode MySQL 5
问题内容: 我真的是数据库新手,并且对一些高级基础知识感兴趣。我已经阅读了这篇精彩的SO文章。在某些情况下,我比别人更好,但不确定为什么。 为什么在联接操作中MySQL比MongoDB快? 为什么MongoDB在分布式系统中可以更好地扩展? 如果我“只是选择一堆表并将所有对象放在一起,这就是大多数人在Web应用程序中所做的事情”,为什么MongoDB会更快? 非常感谢! 问题答案: 这个问题没有任
本文向大家介绍mybatis升级mybatis-plus时踩到的一些坑,包括了mybatis升级mybatis-plus时踩到的一些坑的使用技巧和注意事项,需要的朋友参考一下 前言 最近使用RuoYi-Vue来做后台管理脚手架。RuoYi-Vue 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Spring Security、MyBatis、Jwt、Vue),