最初听说 MongoDB 的时候,我总是觉得它的稳定性堪忧,后来用了差不多一年的时间,其实也没有遇到过什么问题,反而是 MySQL 出现过几次丢失数据的情况。配合 Node.js 使用 MongoDB 是一件非常舒畅的事情,从前端,到后端,再到数据库,统统全是 JSON.
本文的定位是一篇对 MongoDB 的一个概览性的介绍,告诉你 MongoDB 的特点和功能,以及如果需要了解某个功能,应当搜索什么关键词,并不直接涉及技术细节。
特点
无模式
MongoDB 中的每一条文档,都是一个 JSON 对象,因此你无需预定义一个集合的结构,集合中的每个文档也可以有不同的结构。
异步写入
MongoDB 默认所有的写操作都是『不安全』的,即当请求被 MongoDB 收到时,不等写入操作完成,就返回一个『成功』的响应。
这是默认的行为,当然你设置一些选项,让操作等待等待写入完成后再返回响应。不过对于大多数应用,这种『不安全』已经足够安全了。简单查询
MongoDB 只支持简单的查询,MongoDB 只储存数据,更多的逻辑应该在应用中解决。因此 MongoDB 有着简单的且在各编程语言下高度一致的 API 接口。
无需运维
MongoDB 几乎没有什么选项可以设置,集群也是设置一次之后就可以自动地解决故障,极少需要维护。
安装和备份
MongoDB 的安装很简单,直接在官网下载二进制版本,运行其中的 mongod 即可启动数据库,运行 mongo 即可启动客户端 Shell, 或者你也可以从软件源中安装。
MongoDB 被设计运行于 64bit 的操作系统,在 32bit 的情况下,数据文件最大限制为 2GiB.
MongoDB 在运行时并不会实时地将修改写入磁盘,因此在关闭服务器时需要给 MongoDB 时间将所有数据写入磁盘。当出现服务器突然掉电的情况时,MongoDB 的数据库文件会损坏,需要进行修复才能重新运行,这个过程中会丢失掉电时正在进行的写入操作。在对运行中的 MongoDB 进行备份时,需要使用自带的 mongodump 工具,不能直接复制其数据库文件。
设计文档结构
ObjectID
MongoDB 会为每一个文档默认添加一个名为 _id, 类型为 Object 的字段,这个字段用来唯一地标识每一个文档。这个 ID 通过时间,服务器,进程号被生成,甚至可以认为是全世界唯一的。
除了 MongoDB 默认为 _id 添加 ObjectID, 你也可以自己在文档中创建 ObjectID, 来起到唯一地标识某个对象的功能。
引用关系
例如我们有一个 topic 集合,topic 都是 account 创建的,所以 topics 中要引用来自 accounts 中的 account.
// accounts
{
_id: <ObjectID 1>,
name: "jysperm"
}
// topics
{
_id: <ObjectID 2>,
account_id: <ObjectID 1>
}
嵌入关系
每个 topic 会有一些 reply, 所以在 topic 中可以嵌入 reply.
// topics
{
_id: <ObjectID 2>,
account_id: <ObjectID 1>
replies: [
{
_id: <ObjectID 3>,
content: "xxoo"
}
]
}
引用 Vs 嵌入
在上面第一个例子中,topic 其实也可以嵌入 account 中;而第二个例子中,reply 也可以使用一个新的集合,然后来引用 topic, 那么应该如何选择这两种关系呢。
我们主要从几个出发点来考虑:
查询
我们可以考虑查询 account 时,是否需要他所有的 topic, 或者查询 topic 时,是否需要它所有的 reply.
从查询的角度来考虑,如果需要一同获得这两种数据,那么就应该嵌入,如果不需要,就应该引用。数据的增长性
我们还可以考虑 account 的 topic 数量在今后会有怎样的增长,topic 的 reply 数量会有怎样的增长。
如果增长是没有限度的,那么就应该引用,如果增长是有限的,那么就可以嵌入。对应关系
如果是一对一关系,或者一对多关系,那么可以考虑嵌入,如果是多对多关系,那么应该引用。
原子性
MongoDB 仅在文档层面提供原子性,如果有两个非常敏感的数据需要同时被更新,那么他们有必要存在于同一个文档中。
查询和更新
所谓『增删查改』在 MongoDB 里对应:
- find/findOne: 查询
- update/save: 修改
- insert/save: 新增
- remove: 删除
在 MongoDB 中,操作符以 $
开头,主要分为三类:查询操作符,更新操作符,聚合查询操作符。
查询操作符
用于 find 和 update 中的查询器,如 $gt(大于), $ne(不等于), $in(匹配几个值之一), 逻辑运算:$and, $not.
更新操作符
用于 update 中的更新器,如 $inc(对数字进行增量), $set(修改文档的一部分), $unset(删除一个字段).
MongoDB 对嵌入式的文档和数组有非常好的支持:$addToSet(向集合中添加元素), $push(向数组添加元素), $pull(从数组移除元素).聚合查询(Aggregation)
类似于 SQL 数据库中的 GROUP, 提供统计和计算的功能,要多强大有多强大,毕竟可以直接在数据库中运行 JavaScript 代码,不过因为性能的关系,不适合在应用中频繁调用。
查询命令还有几个选项:
- limit: 限制返回的结果数
- skip: 跳过一些结果
- sort: 对结果进行排序
- fields: 只返回指定的字段
副本集(Replica Set)和分片(Sharding)
MongoDB 的副本集采用一主多从的的集群方式。副本集中只有主节点可以写入,其他节点从主节点同步。当主节点故障时,从节点中会自动推选出一个新的主节点。当故障的节点恢复后,会向其他节点同步到最新的数据,然后成为一个从节点。
MongoDB 的分片是指,按照某个字段的值,将数据分为多份,储存在不同的服务器上。客户端会运行一个 mongos 代替 mongod, mongos 相当于一个路由,会根据请求和分片的设置,将请求拆分后发给不同的服务器,得到结果后再将结果组合起来发给客户端。
分片可以与副本集同时使用,此时,每个分片都是一个副本级,这样可以提供非常高的可用率和扩展性。