语言
<< 返回文章列表

了解 MongoDB 看这一篇就够了(上)

2020年1月8日
美码师
403

作者:美码师

一、简介


MongoDB 是一款流行的开源文档型数据库,从它的命名来看,确实是有一定野心的。MongoDB 的原名一开始来自于 英文单词”Humongous”, 中文含义是指”庞大”,即命名者的意图是可以处理大规模的数据。

但笔者更喜欢称呼它为 “芒果”数据库,除了译音更加相近之外,原因还来自于这几年使用 MongoDB 的两层感觉:

 · 第一层感受是”爽”,使用这个文档数据库的特点是几乎不受什么限制,一方面Json文档式的结构更容易理解,而无Schema约束也让DDL管理更加简单,一切都可以很快速的进行。

 · 第二层感受是“酸爽”,这点相信干运维或是支撑性工作的兄弟感受会比较深刻,MongoDB 由于入门体验”太过于友好”,导致一些团队认为用好这个数据库是个很简单的事情,所以开发兄弟在存量系统上埋一些坑也是正常的事情。所谓交付一时爽,维护火葬场.. 当然了,这句话可能有些过。但这里的潜台词是:与传统的RDBMS数据库一样,MongoDB 在使用上也需要认真的考量和看护,不然的话,会遇到更多的坑。


那么,尽管文档数据库在选型上会让一些团队望而却步,仍然不阻碍该数据库所获得的一些支持,比如 DB-Engine 上的排名:

1578453395850097943.jpg

图-DBEngine排名


在全部的排名中,MongoDB 长期排在第5位(文档数据库排名第1位),同时也是最受欢迎的 NoSQL 数据库。另外,MongoDB 的社区一直比较活跃,加上商业上的驱动(MongoDB于2017年在纳斯达克上市),这些因素都推动了该开源数据库的发展

MongoDB 数据库的一些特性:

 · 面向文档存储,基于JSON/BSON 可表示灵活的数据结构

 · 动态 DDL能力,没有强Schema约束,支持快速迭代

 · 高性能计算,提供基于内存的快速数据查询

 · 容易扩展,利用数据分片可以支持海量数据存储

 · 丰富的功能集,支持二级索引、强大的聚合管道功能,为开发者量身定做的功能,如数据自动老化、固定集合等等。

 · 跨平台版本、支持多语言SDK..

假定你是初次了解 MongoDB,下面的内容将能帮助你对该数据库技术的全貌产生一定的了解。


二、基本模型


数据结构对于一个软件来说是至关重要的,MongoDB 在概念模型上参考了 SQL数据库,但并非完全相同。

关于这点,也有人说,MongoDB 是 NoSQL中最像SQL的数据库..

如下表所示:

1578454257070086200.png

说明

 · database 数据库,与SQL的数据库(database)概念相同,一个数据库包含多个集合(表)

 · collection 集合,相当于SQL中的表(table),一个集合可以存放多个文档(行)。不同之处就在于集合的结构(schema)是动态的,不需要预先声明一个严格的表结构。更重要的是,默认情况下 MongoDB 并不会对写入的数据做任何schema的校验。

 · document 文档,相当于SQL中的行(row),一个文档由多个字段(列)组成,并采用bson(json)格式表示。

 · field 字段,相当于SQL中的列(column),相比普通column的差别在于field的类型可以更加灵活,比如支持嵌套的文档、数组。此外,MongoDB中字段的类型是固定的、区分大小写、并且文档中的字段也是有序的。


另外,SQL 还有一些其他的概念,对应关系如下:


1578454393430083360.png

说明

 · id 主键,MongoDB 默认使用一个id 字段来保证文档的唯一性。

 · reference 引用,勉强可以对应于 外键(foreign key) 的概念,之所以是勉强是因为 reference 并没有实现任何外键的约束,而只是由客户端(driver)自动进行关联查询、转换的一个特殊类型。

 · view 视图,MongoDB 3.4 开始支持视图,和 SQL 的视图没有什么差异,视图是基于表/集合之上进行动态查询的一层对象,可以是虚拟的,也可以是物理的(物化视图)。

 · index 索引,与SQL 的索引相同。

 · $lookup,这是一个聚合操作符,可以用于实现类似 SQL-join 连接的功能

 · transaction 事务,从 MongoDB 4.0 版本开始,提供了对于事务的支持

 · aggregation 聚合,MongoDB 提供了强大的聚合计算框架,group by 是其中的一类聚合操作。


BSON 数据类型


MongoDB 文档可以使用 Javascript 对象表示,从格式上讲,是基于 JSON 的。

一个典型的文档如下:

{

  "_id": 1,

  "name" : { "first" : "John", "last" : "Backus" },

  "contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ],

  "awards" : [

    {

      "award" : "W.W. McDowell Award",

      "year" : 1967,

      "by" : "IEEE Computer Society"

    }, {

      "award" : "Draper Prize",

      "year" : 1993,

      "by" : "National Academy of Engineering"

    }

  ]

曾经,JSON 的出现及流行让 Web 2.0 的数据传输变得非常简单,所以使用 JSON 语法是非常容易让开发者接受的。但是 JSON 也有自己的短板,比如无法支持像日期这样的特定数据类型,因此 MongoDB 实际上使用的是一种扩展式的JSON,叫 BSON(Binary JSON)。

BSON 所支持的数据类型包括:


1578454839072006022.jpg

图-BSON类型


分布式ID


在单机时代,大多数应用可以使用数据库自增式ID 来作为主键。传统的 RDBMS 也都支持这种方式,比如 mysql 可以通过声明 auto_increment来实现自增的主键。但一旦数据实现了分布式存储,这种方式就不再适用了,原因就在于无法保证多个节点上的主键不出现重复。

为了实现分布式数据ID的唯一性保证,应用开发者提出了自己的方案,而大多数方案中都会将ID分段生成,如著名的 snowflake 算法中就同时使用了时间戳、机器号、进程号以及随机数来保证唯一性。

MongoDB 采用 ObjectId 来表示主键的类型,数据库中每个文档都拥有一个_id 字段表示主键。_id 的生成规则如下:


微信图片_20200108111228.jpg

图-ObjecteID

其中包括:

 · 4-byte Unix 时间戳

 · 3-byte 机器 ID

 · 2-byte 进程 ID

 · 3-byte 计数器(初始化随机)

值得一提的是 id 的生成实质上是由客户端(Driver)生成的,这样可以获得更好的随机性,同时降低服务端的负载。当然服务端也会检测写入的文档是否包含id 字段,如果没有就自动生成。


三、操作语法


除了文档模型本身,对于数据的操作命令也是基于JSON/BSON 格式的语法。

比如插入文档的操作:

db.book.insert(

{

  title: "My first blog post",

  published: new Date(),

  tags: [ "NoSQL", "MongoDB" ],

  type: "Work",

  author : "James",

  viewCount: 25,

  commentCount: 2

}

执行文档查找:

db.book.find({author : "James"})

更新文档的命令:

db.book.update(

   {"_id" : ObjectId("5c61301c15338f68639e6802")},

   {"$inc": {"viewCount": 3} }

删除文档的命令:

db.book.remove({"_id":

     ObjectId("5c612b2f15338f68639e67d5")}) 

在传统的SQL语法中,可以限定返回的字段,MongoDB可以使用Projection来表示:

db.book.find({"author": "James"},

    {"_id": 1, "title": 1, "author": 1}) 

实现简单的分页查询:

db.book.find({})

    .sort({"viewCount" : -1})

    .skip(10).limit(5) 

这种基于BSON/JSON 的语法格式并不复杂,它的表达能力或许要比SQL更加强大。与 MongoDB 做法类似的还有 ElasticSearch,后者是搜索数据库的佼佼者。

关于文档操作与 SQL方式完整的对比,官方的文档描述得比较详细:https://docs.mongodb.com/manual/reference/sql-comparison/

那么,一个有趣的问题是 MongoDB 能不能用 SQL进行查询?

当然是可以!

但需要注意这些功能并不是 MongoDB 原生自带的,而需要借由第三方工具平台实现:

客户端使用SQL,可以使用 mongobooster、studio3t 这样的工具

服务端的话,可以看看 presto 之类的一些平台..


四、索引


无疑,索引是一个数据库的关键能力,MongoDB 支持非常丰富的索引类型。利用这些索引,可以实现快速的数据查找,而索引的类型和特性则是针对不同的应用场景设计的。

索引的技术实现依赖于底层的存储引擎,在当前的版本中 MongoDB 使用 wiredTiger 作为默认的引擎。在索引的实现上使用了 B+树的结构,这与其他的传统数据库并没有什么不同。所以这是个好消息,大部分基于SQL数据库的一些索引调优技巧在 MongoDB 上仍然是可行的。


1578455119316018528.jpg

图-B+树

使用 ensureIndexes 可以为集合声明一个普通的索引:

db.book.ensureIndex({author: 1})

author后面的数字 1 代表升序,如果是降序则是 -1

实现复合式(compound)的索引,如下:

db.book.ensureIndex({type: 1, published: 1})

只有对于复合式索引时,索引键的顺序才变得有意义

如果索引的字段是数组类型,该索引就自动成为数组(multikey)索引:

db.book.ensureIndex({tags: 1})

MongoDB 可以在复合索引上包含数组的字段,但最多只能包含一个


索引特性


在声明索引时,还可以通过一些参数化选项来为索引赋予一定的特性,包括:

 · unique=true,表示一个唯一性索引

 · expireAfterSeconds=3600,表示这是一个TTL索引,并且数据将在1小时后老化

 · sparse=true,表示稀疏的索引,仅索引非空(non-null)字段的文档

 · partialFilterExpression: { rating: { $gt: 5 },条件式索引,即满足计算条件的文档才进行索引


索引分类


除了普通索引之外,MongoDB 支持的类型还包括:

 · 哈希(HASH)索引,哈希是另一种快速检索的数据结构,MongoDB 的 HASH 类型分片键会使用哈希索引。

 · 地理空间索引,用于支持快速的地理空间查询,如寻找附近1公里的商家。

 · 文本索引,用于支持快速的全文检索

 · 模糊索引(Wildcard Index),一种基于匹配规则的灵活式索引,在4.2版本开始引入。


索引评估、调优


使用 explain() 命令可以用于查询计划分析,进一步评估索引的效果。如下:


> db.test.explain().find( { a : 5 } )

{

  "queryPlanner" : {

    ...

    "winningPlan" : {

      "stage" : "FETCH",

      "inputStage" : {

        "stage" : "IXSCAN",

        "keyPattern" : {

            "a" : 5

        },

        "indexName" : "a_1",

        "isMultiKey" : false,

        "direction" : "forward",

        "indexBounds" : {"a" : ["[5.0, 5.0]"]}

        }

    }},

   ...

从结果 winningPlan 中可以看出执行计划是否高效,比如:

 · 未能命中索引的结果,会显示COLLSCAN

 · 命中索引的结果,使用IXSCAN

 · 出现了内存排序,显示为 SORT

关于 explain 的结果说明,可以进一步参考文档:

https://docs.mongodb.com/manual/reference/explain-results/index.html