3cd5e187bb3e4edce7704c084576ba41
『浅入浅出』MongoDB 和 WiredTiger

MongoDB 是目前主流的 NoSQL 数据库之一,与关系型数据库和其它的 NoSQL 不同,MongoDB 使用了面向文档的数据存储方式,将数据以类似 JSON 的方式存储在磁盘上,因为项目上的一些历史遗留问题,作者在最近的工作中也不得不经常与 MongoDB 打交道,这也是这篇文章出现的原因。

虽然在之前也对 MongoDB 有所了解,但是真正在项目中大规模使用还是第一次,使用过程中也暴露了大量的问题,不过在这里,我们主要对 MongoDB 中的一些重要概念的原理进行介绍,也会与 MySQL 这种传统的关系型数据库做一个对比,让读者自行判断它们之间的优势和劣势。

概述

MongoDB 虽然也是数据库,但是它与传统的 RDBMS 相比有着巨大的不同,很多开发者都认为或者被灌输了一种思想,MongoDB 这种无 Scheme 的数据库相比 RDBMS 有着巨大的性能提升,这个判断其实是一种误解;因为数据库的性能不止与数据库本身的设计有关系,还与开发者对表结构和索引的设计、存储引擎的选择和业务有着巨大的关系,如果认为仅进行了数据库的替换就能得到数量级的性能提升,那还是太年轻了

架构

现有流行的数据库其实都有着非常相似的架构,MongoDB 其实就与 MySQL 中的架构相差不多,底层都使用了『可插拔』的存储引擎以满足用户的不同需要。

用户可以根据表中的数据特征选择不同的存储引擎,它们可以在同一个 MongoDB 的实例中使用;在最新版本的 MongoDB 中使用了 WiredTiger 作为默认的存储引擎,WiredTiger 提供了不同粒度的并发控制和压缩机制,能够为不同种类的应用提供了最好的性能和存储效率。

在不同的存储引擎上层的就是 MongoDB 的数据模型和查询语言了,与关系型数据库不同,由于 MongoDB 对数据的存储与 RDBMS 有较大的差异,所以它创建了一套不同的查询语言;虽然 MongoDB 查询语言非常强大,支持的功能也很多,同时也是可编程的,不过其中包含的内容非常繁杂、API 设计也不是非常优雅,所以还是需要一些学习成本的,对于长时间使用 MySQL 的开发者肯定会有些不习惯。

db.collection.updateMany(
   <filter>,
   <update>,
   {
     upsert: <boolean>,
     writeConcern: <document>,
     collation: <document>
   }
)

查询语言的复杂是因为 MongoDB 支持了很多的数据类型,同时每一条数据记录也就是文档有着非常复杂的结构,这点是从设计上就没有办法避免的,所以还需要使用 MongoDB 的开发者花一些时间去学习各种各样的 API。

RDBMS 与 MongoDB

MongoDB 使用面向文档的的数据模型,导致很多概念都与 RDBMS 有一些差别,虽然从总体上来看两者都有相对应的概念,不过概念之间细微的差别其实也会影响我们对 MongoDB 的理解:

传统的 RDBMS 其实使用 Table 的格式将数据逻辑地存储在一张二维的表中,其中不包括任何复杂的数据结构,但是由于 MongoDB 支持嵌入文档、数组和哈希等多种复杂数据结构的使用,所以它最终将所有的数据以 BSON 的数据格式存储起来。

RDBMS 和 MongoDB 中的概念都有着相互对应的关系,数据库、表、行和索引的概念在两中数据库中都非常相似,唯独最后的 JOINEmbedded Document 或者 Reference 有着巨大的差别。这一点差别其实也影响了在使用 MongoDB 时对集合(Collection)Schema 的设计,如果我们在 MongoDB 中遵循了与 RDBMS 中相同的思想对 Collection 进行设计,那么就不可避免的使用很多的 “JOIN” 语句,而 MongoDB 是不支持 “JOIN” 的,在应用内做这种查询的性能非常非常差,在这时使用嵌入式的文档其实就可以解决这种问题了,嵌入式的文档虽然可能会造成很多的数据冗余导致我们在更新时会很痛苦,但是查询时确实非常迅速。

{
  _id: <ObjectId1>,
  name: "draveness",
  books: [
    {
      _id: <ObjectId2>,
      name: "MongoDB: The Definitive Guide"
    },
    {
      _id: <ObjectId3>,
      name: "High Performance MySQL"
    }
  ]
}

在 MongoDB 的使用时,我们一定要忘记很多 RDBMS 中对于表设计的规则,同时想清楚 MongoDB 的优势,仔细思考如何对表进行设计才能利用 MongoDB 提供的诸多特性提升查询的效率。

数据模型

MongoDB 与 RDBMS 之间最大的不同,就是数据模型的设计有着非常明显的差异,数据模型的不同决定了它有着非常不同的特性,存储在 MongoDB 中的数据有着非常灵活的 Schema,我们不需要像 RDBMS 一样,在插入数据之前就决定并且定义表中的数据结构,MongoDB 的结合不对 Collection 的数据结构进行任何限制,但是在实际使用中,同一个 Collection 中的大多数文档都具有类似的结构。

在为 MongoDB 应用设计数据模型时,如何表示数据模型之间的关系其实是需要开发者需要仔细考虑的,MongoDB 为表示文档之间的关系提供了两种不同的方法:引用和嵌入。

标准化数据模型

引用(Reference)在 MongoDB 中被称为标准化的数据模型,它与 MySQL 的外键非常相似,每一个文档都可以通过一个 xx_id 的字段『链接』到其他的文档:

但是 MongoDB 中的这种引用不像 MySQL 中可以直接通过 JOIN 进行查找,我们需要使用额外的查询找到该引用对应的模型,这虽然提供了更多的灵活性,不过由于增加了客户端和 MongoDB 之间的交互次数(Round-Trip)也会导致查询变慢,甚至非常严重的性能问题。

MongoDB 中的引用并不会对引用对应的数据模型是否真正存在做出任何的约束,所以如果在应用层级没有对文档之间的关系有所约束,那么就可能会出现引用了指向不存在的文档的问题:

虽然引用有着比较严重的性能问题并且在数据库层面没有对模型是否被删除加上限制,不过它提供的一些特点是嵌入式的文档无法给予了,当我们需要表示多对多关系或者更加庞大的数据集时,就可以考虑使用标准化的数据模型 — 引用了。

嵌入式数据模型

除了与 MySQL 中非常相似的引用,MongoDB 由于其独特的数据存储方式,还提供了嵌入式的数据模型,嵌入式的数据模型也被认为是不标准的数据模型:

因为 MongoDB 使用 BSON 的数据格式对数据进行存储,而嵌入式数据模型中的子文档其实就是父文档中的另一个值,只是其中存储的是一个对象:

{
  _id: <ObjectId1>,
  username: "draveness",
  age: 20,
  contact: [
    {
      _id: <ObjectId2>,
      email: "i@draveness.me"
    }
  ]
}

嵌入式的数据模型允许我们将有相同的关系的信息存储在同一个数据记录中,这样应用就可以更快地对相关的数据进行查询和更新了;当我们的数据模型中有『包含』这样的关系或者模型经常需要与其他模型一起出现(查询)时,比如文章和评论,那么就可以考虑使用嵌入式的关系对数据模型进行设计。

top Created with Sketch.