快速入门
MongoDB 是当今业界使用最广泛的文档数据库之一,其从 2009 年诞生以来,已经吸引了无数开发者的目光。
曾经 MongoDB 被冠以 “四不像” 的称号,被大家称为“非关系型数据库中最像关系型数据库的软件”。即便如此,MongoDB 在其发展历程中仍表现出强大的生命力。
尤其是在近几年,随着云计算、大数据的飞速发展,企业项目对分布式数据库的需求越来越多,MongoDB 作为一款灵活易用、高可用、高可扩展的分布式数据库,在许多互联网产品及企业项目中大施拳脚。
采用传统的关系型数据库可能是一种“万金油”的方案,选择 MongoDB 则很大程序取决于团队对 NoSQL 的接受程度,或是来自敏捷开发、高效扩展方面的权衡。
MongoDB 的名字来自英文单词 "Humongous",中文含义是 "庞大" "巨大",命名者的意图是可以处理大规模的数据。
使用 MongoDB 的两层感受:
- 第一层感受是“爽”。相比关系型数据库,MongoDB 几乎没有太多的约束。一方面,MongoDB 的文档模型是基于 JSON 的,开发者更容易理解。
另一方面,动态化模式的特性让数据库的管理工作变得更加简单,例如一些线上的变更可以更快速地完成。 - 第二层感受是“酸爽”。这一点对于 MongoDB 数据管理员来说可能更有感触一些。MongoDB 由于入门体验“太过于友好”,导致初学者很容易产生一种误解,即 MongoDB 不需要在管理方面投入太多的精力,最终导致系统上线后不断被发现一些技术债务。与传统的关系数据库一样,MongoDB 在使用上仍然需要认真地考量和看护,只有如此才可能最大限度地发挥出 MongoDB 的优势
https://db-engines.com/en/article/Document+Stores
MongoDB 是一个可移植的数据库,在主流的操作系统上都可以使用,即跨平台特性。
虽然在不同的操作系统上略有差别,但是从整体架构上来看,MongoDB 在不同平台上是一样的,如数据逻辑结构和数据存储方式等。
MongoDB Shell 是 MongoDB 自带的交互式 JavaScript Shell,是用来对 MongoDB 执行操作和管理的交互式环境。
使用./mongo-help
命令可查看相关参数及说明
认识MongoDB
MongoDB 是 NoSQL 数据库中的佼佼者,目前是排名第一的文档型数据库。该数据库基于灵活的 JSON 文档模型,非常适合敏捷式的快速开发。
与此同时,其与生俱来的高可用、高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。
面向文档设计
在我们的系统中,通常会用分层来描述现实中的模型,如图 1-1 所示:
从上往下看,每一层都提供了更简单、更容易表述的模型来隐藏下层的复杂性。
最为典型的是,数据库系统屏蔽了所有磁盘中文件如何存取、压缩/解压缩等细节,向应用程序展示了一些通用的数据模型,如 SQL 表、列,或是基于 JSON、XML 的文档模型。
而应用程序方面,面向对象的模型也已经被绝大多数人所熟知并接受。
在处理 SQL 数据模型时,应用程序需要通过代码做一些必要的转换工作,一般可以借助一些 ORM 框架来减少工作量。
然而,SQL 模型与面向对象之间仍然存在不少差异,这些差异并不能完全通过框架屏蔽。
相较之下,基于 JSON 的文档模型则更能契合面向对象的设计准则,对于开发者来说,这在一定程序上降低了使用数据的门槛。
MongoDB 是基于 JSON 来描述数据的,所有的“数据行”都可以通过一个 JSON 格式的文档(document)来表示。比如下面的例子:
1 2 3 4 5 6 7 8 9 10 |
|
很明显,基于 JSON 格式的数据模型可读性非常强,也更加灵活;除了基本的数据类型,文档中还可以使用数组、内嵌子对象等高级的字段类型。
文档中还可以使用数组、内嵌子对象等高级的字段类型。
此外,JSON 还具备无模式(模式灵活)的特点,可以轻松地进行扩展。在访问 MongoDB 的“表”之前,并不需要事先对表模型进行声明(尽管你也可以这么做)。
同时,当数据模型发生变更时,MongoDB 也不会强制要求你去执行表结构更新的相关操作,这提供了很大的便利性。
在 MongoDB 内部,BSON(一种二进制版本的 JSON 扩展)被真正用来存储这些 JSON 形式的文档数据。
在 JSON 的基础纸上,BSON 进行了一些易用性方面的扩展,例如增加日期、二进制等类型的支持。
虽然 Mnogo “沿袭” 了 JSON 的特点,但将它归类为无模式数据库是不恰当的。
实际上,所有的读写都是基于一种内部隐含的模式,模式采取按需变更而非提前声明,因此动态模式一词更适合它
特性
完备的索引:
与大多数数据库一样,MongoDB 支持各种丰富的索引类型,包括单键索引、复合索引、唯一索引等一些常用的结构。
由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过建立合理的索引,我们可以极大地提升数据的检索速度。
值得一提的是,MongoDB 的索引实现与一般的关系型数据库索引并没有太多不同,因此,我们几乎可以使用某种“一致的思路”来设计索引或完成一些性能调优的任务。
在一些特殊应用场景,MongoDB 还支持地理空间索引、文本检索索引、TTL 索引等不同的特性,这些特性在很大程度上简化了应用程序的开发工作,同时也使 MongoDB 获得了大量使用的青睐。
跨平台,支持各种编程语言
MongoDB 是用 C++ 语言编写的,其官方网站提供了各种平台的编译版本,你可以在 Wiinndows 或几乎任意一个 Linux 发行版本中安装及允许 MongoDB 数据库。
在客户端方面,MongoDB 提供了多种编程语言实现的驱动程序,除了 Java、C/C++/C#
等传统语言,像 Python、NodeJS 等动态语言也都有对应的实现。
强大的聚合计算:
聚合(aggregation)计算是 MongoDB 面向数据分析领域的重要特性,可以用于实现数据的分类统计或一些管道计算
作为对照,聚合框架能轻松完成关系型数据库的 group by 语句的分组功能,又或是大数据领域的 map-reduce 计算。
可能存在的一点区别就是,MongoDB 聚合框架是以文档化模型为基础来设计的,更适合非结构化数据
MongoDB 为聚合框架提供了大量常用的函数以简化开发,除此之外,聚合框架还用到了一种叫“管道”(pipeline)的概念,用于抽象各个数据处理的阶段,一个管道由多个“阶段”(stage)组成,通过对不同的阶段进行自由组合,我们就可以灵活应对各种场景中的计算需求。
复制、分布式:
MongoDB 通过副本集(replication set)来实现数据库的高可用,这点类似于 MySQL 的 Master/Slave 复制架构,不同的是,一个副本集可以由一个主节点和多个备节点组成,主节点和备节点基于 oplog 来实现数据同步。
在主节点发生故障时,备节点将重新选举出新的主节点以继续提供服务,整个切换过程是自动完成的。
在海量数据处理方面,MongoDB 原生就支持分布式计算能力。
在一个分布式集群中,多个文档备划入一个逻辑数据块(chunk),这些数据块可以备存储于不同的就算节点(分片)上,在新的计算节点(分片)加入时,数据块可以借助自动均衡的算法机制备迁移到合适的位置(通常是压力较小的分片)。
通过这种自动化的调度及均衡工作,整个集群的数据库读写压力可以被分摊到多个节点上,从而实现负载均衡和水平扩展。
体系结构
一个运行着的 MongoDB 数据库可以看作一个 MongoDB Server,该 Server 由实例和数据库组成。
一般情况下,一个 MongoDB Server 包含一个实例和多个与之对应的数据库,但是在特殊情况下,如硬件投入成本有限或特殊的应用需求,也允许一个 Server 机器上有多个实例和多个数据库
MongoDB 中一系列物理文件(数据文件、日志文件等)的集合或与之对应的逻辑结构(集合、文档等)称为数据库。简单来说,数据库是由一系列与磁盘有关的物理文件组成的
数据逻辑结构
MongoDB 数据逻辑结构是面向用户的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
MongoDB 逻辑结构是一种层次结构,主要由文档(document)、集合(collection)、数据库(database)组成
文档、集合、数据库三部分的关系如下:
- MongoDB 的文档相当于关系数据中的一条记录
- 多个文档组成一个结合,相当于关系数据库中的表
- 多个集合逻辑上组织在一起就是数据库
- 一个 MongoDB 实例支持多个数据库
数据存储结构
MongoDB 存储数据的默认目录是/data/db
,这个目录负责存储所有 MongoDB 的数据文件。
在 MongoDB 内部,每个数据库都包含一个 ns 文件和一些数据文件,这些数据文件会随着数据量的增加而变得越来越多。
例如,系统中有一个名为 kon 的数据库,那么构成 kon 数据库的文件就由 kon.ns、kon.0、kon.1、kon.2 等组成。
MongoDB 内部有预分配空间的机制,每个预分配的文件都用 0 进行填充,这使 MongoDB 始终保持额外的空间和空余的数据文件,有效避免了由于数据暴增而带来的磁盘压力过大的问题。
如果想避免预分配,可以在 mongod 启动时,加上参数--noprealloc
,这样,系统的预分配机制就失效了
由于表中数据量的增加,数据文件每分配一次,它的大小都会是上一个数据文件大小的 2 倍,每个数据文件最大 2GB,这样的机制有利于防止较小的数据库浪费过多的磁盘空间,同时又能保证较大的数据库有相应的预留空间使用
日志系统
任何一种数据库都有各种各样的日志,MongoDB 也不例外。MongoDB 中有几种日志,分别是系统日志、Journal 日志、oplog 主从日志、慢查询日志等。
这些日志记录着 MnogoDB 数据库不同方面的踪迹
系统日志
系统日志在 MongoDB 数据库中很重要,它记录着 MongoDB 启动和停止的操作,以及服务器在运行过程中发生的任何异常信息。
配置系统日志的方法比较简单,只需要在启动 mongod 时指定一个 logpath 参数即可,例如
1 |
|
系统日志会向logpath
指定的文件中持续追加
Journal 日志
Journal 日志通过预写式的 redo 日志为 MongoDB 增加了额外的可靠性保障。开启该功能时,数据的更新会先写入 Journal 日志,定期集中提交(目前是每 100ms 提交一次),然后在真实数据上执行这些变更。如果服务器安全关闭,日志会被清楚。
在服务器启动时,如果存在 Journal 日志,则会执行提交。这保证了那些已经写入 Journal 日志但在服务器崩溃前还没有提交的操作能在用户连接服务器前被执行,两次提交之间的 100ms 时间窗口,在未来的版本中有望被缩小
启用数据库的 Journal 功能非常简单,只需在 mongod 后面指定 jouranl 参数即可
1 |
|
这样,系统的 Journal 信息都会放到数据库目录(默认是/data/db
)的 jouranl 文件夹中
oplog 主从日志
MongoDB 的高可用复制策略中有一种叫做 Repica Sets。Replica Sets 复制过程中一个服务器充当主服务器,而一个或多个服务器充当从服务器,主服务器将更新写入一个本地的 collection 中,这个 collection 记录着发生在主服务器的更新操作,并将这些操作分发到从服务器上
这个日志是一个 Capped Collection 且有大小之分,所以最好在 mongod 启动时配置好大小(单位:MB)。例如以下语句
1 |
|
慢查询日志
慢查询日志记录了执行时间超过所设定时间阈值的操作语句。
慢查询日志对于发现性能有问题的语句很有帮助,建议开启此功能并经常分析该日志的内容。
要想配置这个功能只需要在 mongod 启动时指定 profile 参数即可。例如,想要将超过 5 秒的操作都记录,可以使用如下语句
1 |
|
系统运行一段时间后,可以通过查看db.system.profile
这个 collection 来获取慢日志信息
元数据的存储
元数据是一个预留空间,在对数据库或应用程序结构执行修改时,其内容可以由数据库自动更新。
元数据是系统中各类数据描述的集合,是执行详细的数据收集和数据分析的主要途径。
元数据最重要的作用是作为分析阶段的工具。
任何字典最重要的用途都是查询,在结构化分析中,元数据的作用是给数据流图上的每个节点加以定义和说明。
换句话说,数据流图上所有节点的定义和解释的集合就是元数据,而且在元数据中建立严密一致的定义有助于提高需求分析人员和用户沟通的效率
MongoDB 跟其他数据库一样,有专门存储元数据的系统表。
<dbname>.system.*namespaces
是一个特殊的对象,其中存储数据库的系统信息,通过这些表用户可以大概了解系统中数据库对象的情况,例如:
system.namespaces
(存储命名空间信息)system.indexes
(存储索引信息)system.profile
(存储优化器信息)system.users
(存储用户信息)local.sources
(存储复制集的配置信息和状态信息)
其他的命名空间和索引信息都存储在 database.ns
文件中,并且对用户是封装的。
注意,"$"
是一个保留自负,不要再命名空间和列名中使用它
数据类型
MongoDB 中的文档可以理解为 JSON 类型的对象。JSON 的格式很简单,只支持 6 种数据类型,容易理解、解析和记忆。
MongoDB 在采取了 JSON 键值对的同时,还支持很多额外的数据类型。
数据类型 | 描述 | 形式 |
---|---|---|
Null | 代表空值和不存在的值 | {"x": null} |
Boolean | 只有 true 和 false | {"x": true} |
String | 任意 UTF-8 自负 | |
Symbol | Shell 不支持,会转化为 string | {"x": "foobar"} |
object id | 唯一的 12 位ID | {"x": ObjectId()} |
date | 保存从纪元开始到现在的毫秒数,不支持时区 | {"x": new Date()} |
regular expression | 采用 JavaScript 的正则表达式语法 | {"x": /foobar/i} |
code | 可以包含 JavaScript 代码 | {"x": function() {/* .. */}} |
array | 列表 | {"x": ["a", "b", "c"]} |
embedded document | 嵌套的文档 | {"x": {"foo": "bar"}} |
尽管这些类型在一般场合下是足够的,但还是存在其他很多的数据类型 JSON 是不支持的。
例如 JSON 没有日期类型,对日期的处理要烦琐一些;它有数字类型,但无法区分浮点型和整型的数值,更不用说严格区分 32 位和 64 位了;
此外,正则表达式和函数类型也无法表现
操作数据库
1 2 3 4 5 |
|
连接数据库
1 2 |
|
插入记录
1 2 3 4 5 6 7 8 9 10 11 |
|
以下几点需要注意:
- 不需要预先创建一个集合,在第一次插入数据时会自动创建
- 在文档中可以存储任何结构的数据,但在实际应用中存储的还是相同类型文档的集合。此特性很灵活,不需要类似 alter table 语句来修改数据结构
- 每次插入数据时,集合中都会有一个 ID(_id)
下面插入一些数据,如下面的代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
查询记录
先看怎样从查询中返回一个游标对象。可以简单地通过 find() 来查询,返回一个任意结构的集合
普通查询
一般的查询可以通过 while 循环输出
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上面的例子显示了游标风格的迭代输出,hasNext()
函数用于判断是否还有数据,如果有则调用next()
函数将数据取出来。
当使用的是 JavaScript Shell 时,可以用 JavaScript 的 forEach 特性,这样就可以输出游标了。
下面的例子就是使用forEach()
循环输出数据,但forEach()
必须定义一个函数供每个游标元素调用,如下面的代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
使用游标时请注意占用内存的问题,特别是很大的游标对象,有可能会内存溢出,所以英爱用迭代的方式来输出。
下面的示例是把游标转换成真实的数组类型:
1 2 3 |
|
注意:这些特性只是在 MongoDB Shell 里使用,不是所有的其他应用程序驱动都支持。
如果有其他用户在集合里第一次或者最后一次调用 next(),可能得不到游标里的数据,所以要明确地锁定要查询的游标
条件查询
1 2 3 4 5 6 7 8 9 10 |
|
上面显示的是所有元素,也可以返回特定的元素,类似于返回表里某字段的值,只需要在"find({x: 4})"
里指定元素的名称,如下所示:
1 2 3 4 5 6 7 8 9 10 |
|
findOne()语法
MongoDB Shell 为了避免游标可能带来的开销,提供了一个findOne()
函数。
这个函数和find()
函数一样,不过它返回的是游标里的第一条数据,或者返回null
,即空数据
例如name="mongo"
可以用很多方法来实现,如用next()
来循环游标或者当成数组返回第一个元素,而用findOne()
方法更简单和高效,如下面的代码所示:
1 2 |
|
通过 limit 限制结果集数量
1 2 3 4 |
|
强烈推荐这种解决性能问题的方法,通过限制条数来减少网络传输,同时,limit 方法也广泛应用于分页技术中
修改记录
例如,将列name
的值从'mongo'
修改为'mongo_new'
,如下面的代码所示:
1 2 3 4 5 6 7 8 9 |
|
删除记录
1 2 |
|