MongoDB【快速入门】

1.MongDB 简介

MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是能够应用于各类规模的企业、各个行业以及各种应用程序的开源数据库。做为一个适用于敏捷开发的数据库,MongoDB 的数据模式能够随着应用程序的发展而灵活地更新。与此同时,它也为开发人员 提供了传统数据库的功能:二级索引,完整的查询系统以及严格一致性等等。 MongoDB 可以使企业更加具备敏捷性和可扩展性,各类规模的企业均可以经过使用 MongoDB 来建立新的应用,提升与客户之间的工做效率,加快产品上市时间,以及下降企业成本。html

MongoDB 是专为可扩展性,高性能和高可用性而设计的数据库。它能够从单服务器部署扩展到大型、复杂的多数据中心架构。利用内存计算的优点,MongoDB 可以提供高性能的数据读写操做。 MongoDB 的本地复制和自动故障转移功能使您的应用程序具备企业级的可靠性和操做灵活性。git

以上内容摘自官网:github

1.1 文档型数据库

简而言之,MongoDB是一个免费开源跨平台的 NoSQL 数据库,与关系型数据库不一样,MongoDB 的数据以相似于 JSON 格式的二进制文档存储:mongodb

{
    name: "我没有三颗心脏",
    age: 22,
}

文档型的数据存储方式有几个重要好处:shell

  1. 文档的数据类型能够对应到语言的数据类型,如数组类型(Array)和对象类型(Object);
  2. 文档能够嵌套,有时关系型数据库涉及几个表的操做,在 MongoDB 中一次就能完成,能够减小昂贵的链接花销;
  3. 文档不对数据结构加以限制,不一样的数据结构能够存储在同一张表;
  4. MongoDB 的文档数据模型和索引系统能有效提高数据库性能;
  5. 复制集功能提供数据冗余,自动化容灾容错,提高数据库可用性;
  6. 分片技术可以分散单服务器的读写压力,提升并发能力,提高数据库的可拓展性;
  7. MongoDB 高性能,高可用性、可扩展性等特色,使其至 2009 年发布以来,逐渐被承认,并被愈来愈多的用于生产环境中。AWS、GCP、阿里云等云平台都提供了十分便捷的 MongoDB 云服务。

1.2 MongoDB 基础概念

可使用咱们熟悉的 MySQL 数据库来加以对比:数据库

MySQL 基础概念 MongoDB 对应概念
数据库(database) 容器(database)
表(table) 集合(collection)
行(row) 文档(document)
列(column) 域(filed)
索引(index) 索引(index)

也借用一下菜鸟教程的图来更加形象生动的说明一下:json

这很容易理解,可是问题在于:咱们为何要引入新的概念呢?(也就是为何咱们要把“表”替换成“集合”,“行”替换成“文档”,“列”替换成“域”呢?)缘由在于,其实在 MySQL 这样的典型关系型数据中,咱们是在定义表的时候定义列的,可是因为上述文档型数据库的特色,它容许文档的数据类型能够对应到语言的数据类型,因此咱们是在定义文档的时候才会定义域的。segmentfault

也就是说,集合中的每一个文档均可以有独立的域。所以,虽然说集合相对于表来讲是一个简化了的容器,而文档则包含了比行要多得多的信息。数组

2 搭建环境

怎么样都好,搭建好环境就行,这里以 OS 环境为例,你可使用 OSX 的 brew 安装 mongodb:安全

brew install mongodb

在运行以前咱们须要建立一个数据库存储目录 /data/db

sudo mkdir -p /data/db

而后启动 mongodb,默认数据库目录即为 /data/db(若是不是,可使用 --dbpath 指令来指定):

sudo mongd

过一下子你就能看到你的 mongodb 运行起来的提示:

具体的搭建过程能够参考菜鸟的教程:http://www.runoob.com/mongodb/mongodb-window-install.html

3 基于 Shell 的 CRUD

3.1 链接实例

经过上面的步骤咱们在系统里运行了一个 mongodb 实例,接下来经过 mongo 命令来链接它:

mongo [options] [db address] [file names]

因为上面运行的 mongodb 运行在 27017 端口,而且灭有启动安全模式,因此咱们也不须要输入用户名和密码就能够直接链接:

mongo 127.0.0.1:27017

或者经过 --host--port 选项指定主机和端口。一切顺利的话,就进入了 mongoDB shellshell 会报出一连串权限警告,不过不用担忧,这并不会影响以后的操做。在添加受权用户和开启认证后,这些警告会自动消失。

3.2 CRUD 操做

在进行增删改查操做以前,咱们须要先了解一下经常使用的 shell 命令:

  • db 显示当前所在数据库,默认为 test
  • show dbs 列出可用数据库
  • show tables show collections 列出数据库中可用集合
  • use <database> 用于切换数据库

mongoDB 预设有两个数据库,admin 和 local,admin 用来存放系统数据,local 用来存放该实例数据,在副本集中,一个实例的 local 数据库对于其它实例是不可见的。使用 use 命令切换数据库:

> use admin
> use local
> use newDatabase

能够 use 一个不存在的数据库,当你存入新数据时,mongoDB 会建立这个数据库:

> use newDatabase
> db.newCollection.insert({x:1})
WriteResult({ "nInserted" : 1 })

以上命令向数据库中插入一个文档,返回 1 表示插入成功,mongoDB 自动建立 newCollection 集合和数据库 newDatabase。下面将对增查改删操做进行一个简单的演示。

3.2.1 建立(Create)

MongoDB 提供 insert 方法建立新文档:

  • db.collection.inserOne() 插入单个文档
    WriteResult({ "nInserted" : 1 })
  • db.collection.inserMany() 插入多个文档
  • db.collection.insert() 插入单条或多条文档

咱们接着在刚才新建立的 newDatabase 下面新增数据吧:

db.newCollection.insert({name:"wmyskxz",age:22})

根据以往经验应该会以为蛮奇怪的,由于以前在这个集合中插入的数据格式是 {x:1} 的,而这里新增的数据格式确是 {name:"wmyskxz",age:22} 这个样子的。还记得吗,文档型数据库的与传统型的关系型数据的区别就是在这里!

而且要注意,age:22age:"22" 是不同的哦,前者插入的是一个数值,然后者是字符串,咱们能够经过 db.newCollection.find() 命令查看到刚刚插入的文档:

> db.newCollection.find()
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }

这里有一个神奇的返回,那就是多了一个叫作 _id 的东西,这是 MongoDB 为你自动添加的字段,你也能够本身生成。大部分状况下仍是会让 MongoDB 为咱们生成,并且默认状况下,该字段是被加上了索引的。

3.2.2 查找(Read)

MongoDB 提供 find 方法查找文档,第一个参数为查询条件:

> db.newCollection.find() # 查找全部文档
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }
> db.newCollection.find({name:"wmyskxz"}) # 查找 name 为 wmyskxz 的文档
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }
> db.newCollection.find({age:{$gt:20}}) # 查找 age 大于 20 的文档
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }

上述代码中的$gt对应于大于号>的转义。

第二个参数能够传入投影文档映射数据:

> db.newCollection.find({age:{$gt:20}},{name:1})
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz" }

上述命令将查找 age 大于 20 的文档,返回 name 字段,排除其余字段。投影文档中字段为 1 或其余真值表示包含,0 或假值表示排除,能够设置多个字段位为 1 或 0,但不能混合使用。

为了测试,咱们为这个集合弄了一些奇奇怪怪的数据:

> db.newCollection.find()
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30 }

而后再来测试:

> db.newCollection.find({age:{$gt:20}},{name:1,x:1}) 
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz" }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "x" : 1 }
> db.newCollection.find({age:{$gt:20}},{name:0,x:0}) 
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "age" : 22 }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "age" : 22, "y" : 30 }
> db.newCollection.find({age:{$gt:20}},{name:0,x:1})
Error: error: {
    "ok" : 0,
    "errmsg" : "Projection cannot have a mix of inclusion and exclusion.",
    "code" : 2,
    "codeName" : "BadValue"
}

从上面的命令咱们就能够把咱们的一些想法和上面的结论得以验证,perfect!

除此以外,还能够经过 countskiplimit 等指针(Cursor)方法,改变文档查询的执行方式:

> db.newCollection.find().count()
3
> db.newCollection.find().skip(1).limit(10).sort({age:1})
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30 }

上述查找命令跳过 1 个文档,限制输出 10 个,以 age 子段正序排序(大于 0 为正序,小于 0 位反序)输出结果。最后,可使用 Cursor 方法中的 pretty 方法,提高查询文档的易读性,特别是在查看嵌套的文档和配置文件的时候:

> db.newCollection.find().pretty()
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{
    "_id" : ObjectId("5cc102fb33907ae66490e46d"),
    "name" : "wmyskxz",
    "age" : 22
}
{
    "_id" : ObjectId("5cc108fb33907ae66490e46e"),
    "name" : "wmyskxz-test",
    "age" : 22,
    "x" : 1,
    "y" : 30
}

3.2.3 更新(Update)

MongoDB 提供 update 方法更新文档:

  • db.collection.updateOne() 更新最多一个符合条件的文档
  • db.collection.updateMany() 更新全部符合条件的文档
  • db.collection.replaceOne() 替代最多一个符合条件的文档
  • db.collection.update() 默认更新一个文档,可配置 multi 参数,跟新多个文档

update() 方法为例。其格式:

> db.collection.update(
    <query>,
    <update>,
    {
        upsert: <boolean>,
        multi: <boolean>
    }
)

各参数意义:

  • query 为查询条件
  • update 为修改的文档
  • upsert 为真,查询为空时插入文档
  • multi 为真,更新全部符合条件的文档

下面咱们测试把 name 字段为 wmyskxz 的文档更新一下试试:

> db.newCollection.update({name:"wmyskxz"},{name:"wmyskxz",age:30})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

要注意的是,若是更新文档只传入 age 字段,那么文档会被更新为{age: 30},而不是{name:"wmyskxz", age:30}。要避免文档被覆盖,须要用到 $set 指令,$set 仅替换或添加指定字段:

> db.newCollection.update({name:"wmyskxz"},{$set:{age:30}})

若是要在查询的文档不存在的时候插入文档,要把 upsert 参数设置真值:

> db.newCollection.update({name:"wmyskxz11"},{$set:{age:30}},{upsert:true})

update 方法默认状况只更新一个文档,若是要更新符合条件的全部文档,要把 multi 设为真值,并使用 $set 指令:

> db.newCollection.update({age:{$gt:20}},{$set:{test:"A"}},{multi:true})
WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 3 })
> db.newCollection.find()
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{ "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 30, "test" : "A" }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30, "test" : "A" }
{ "_id" : ObjectId("5cc110148d0a578f03d43e81"), "name" : "wmyskxz11", "age" : 30, "test" : "A" }

3.2.4 删除(Delete)

MongoDB 提供了 delete 方法删除文档:

  • db.collection.deleteOne() 删除最多一个符合条件的文档
  • db.collection.deleteMany() 删除全部符合条件的文档
  • db.collection.remove() 删除一个或多个文档

以 remove 方法为例:

> db.newCollection.remove({name:"wmyskxz11"})
> db.newCollection.remove({age:{$gt:20}},{justOne:true})
> db.newCollection.find()
{ "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 }
{ "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30, "test" : "A" }

MongoDB 提供了 drop 方法删除集合,返回 true 表面删除集合成功:

> db.newCollection.drop()

3.2.5 小结

相比传统关系型数据库,MongoDB 的 CURD 操做更像是编写程序,更符合开发人员的直觉,不过 MongoDB 一样也支持 SQL 语言。MongoDB 的 CURD 引擎配合索引技术、数据聚合技术和 JavaScript 引擎,赋予 MongoDB 用户更强大的操纵数据的能力。

参考文章:简明 MongoDB 入门教程 - https://segmentfault.com/a/1190000010556670

4 MongoDB 数据模型的一些讨论

前置申明:这一部分基于如下连接整理 https://github.com/justinyhuang/the-little-mongodb-book-cn/blob/master/mongodb.md#%E8%AE%B8%E5%8F%AF%E8%AF%81

这是一个抽象的话题,与大多数NoSQL方案相比,在建模方面,面向文档的数据库算是和关系数据库相差最小的。这些差异是很小,可是并非说不重要。

4.1 没有链接(Join)

您要接受的第一个也是最基本的一个差异,就是 MongoDB 没有链接(join)。我不知道MongoDB不支持某些类型链接句法的具体缘由,可是我知道通常而言人们认为链接是不可扩展的。也就是说,一旦开始横向分割数据,最终不可避免的就是在客户端(应用程序服务器)使用链接。且不论MongoDB为何不支持链接,事实是数据是有关系的,但是MongoDB不支持链接。(译者:这里的关系指的是不一样的数据之间是有关联的,对于没有关系的数据,就彻底不须要链接。)

为了在没有链接的MongoDB中生存下去,在没有其余帮助的状况下,咱们必须在本身的应用程序中实现链接。

基本上咱们须要用第二次查询去找到相关的数据。找到并组织这些数据至关于在关系数据库中声明一个外来的键。如今先别管什么独角兽了,咱们来看看咱们的员工。首先咱们建立一个员工的数据(此次我告诉您具体的_id值,这样咱们的例子就是同样的了):

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d730"), name: 'Leto'})

而后咱们再加入几个员工并把 Leto 设成他们的老板:

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d731"), name: 'Duncan', manager: ObjectId("4d85c7039ab0fd70a117d730")});
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d732"), name: 'Moneo', manager: ObjectId("4d85c7039ab0fd70a117d730")});

*(有必要再强调一下,_id能够是任何的惟一的值。在实际工做中你极可能会用到ObjectId, 因此咱们在这里也使用它)*

显然,要找到Leto的全部员工,只要执行:

db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})

没什么了不得的。在最糟糕的状况下,为弥补链接的缺失须要作的只是再多查询一次而已,该查询极可能是通过索引了的。

4.1.1 数组和嵌入文档(Embedded Documents)

MongoDB 没有链接并不意味着它没有其余的优点。还记得咱们曾说过 MongoDB 支持数组并把它当成文档中的一级对象吗?当处理多对一或是多对多关系的时候,这一特性就显得很是好用了。用一个简单的例子来讲明,若是一个员工有两个经理,咱们能够把这个关系储存在一个数组当中:

({name: 'Siona', manager: [ObjectId("4d85c7039ab0fd70a117d730"), ObjectId("4d85c7039ab0fd70a117d732")] })

须要注意的是,在这种状况下,有些文档中的 manager 多是一个向量,而其余的倒是数组。在两种状况下,前面的 find 仍是同样能够工做:

db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})

很快您就会发现数组中的值比起多对多的链接表(join-table)来讲要更容易处理。

除了数组,MongoDB 还支持嵌入文档。尝试插入含有内嵌文档的文档,像这样:

db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d734"), name: 'Ghanima', family: {mother: 'Chani', father: 'Paul', brother: ObjectId("4d85c7039ab0fd70a117d730")}})

也许您会这样想,确实也能够这样作:嵌入文档能够用‘.’符号来查询:

db.employees.find({'family.mother': 'Chani'})

就这样,咱们简要地介绍了嵌入文档适用的场合以及您应该怎样使用它。

4.1.2 DBRef

MongoDB 支持一个叫作 DBRef 的功能,许多 MongoDB 的驱动都提供对这一功能的支持。当驱动遇到一个 DBRef 时它会把当中引用的文档读取出来。DBRef 包含了所引用的文档的 ID 和所在的集合。它一般专门用于这样的场合:相同集合中的文档须要引用另一个集合中的不一样文档。例如,文档 1 的 DBRef 可能指向 managers 中的文档,而文档 2 中的 DBRef 可能指向 employees 中的文档。

4.1.3 范规范化(Denormalization)

代替链接的另外一种方法就是反规范化数据。在过去,反规范化是为性能敏感代码所设,或者是须要数据快照(例如审计日志)的时候才应用的。然而,随着NoSQL的日渐普及,有许多这样的数据库并不提供链接操做,因而做为规范建模的一部分,反规范化就愈来愈常见了。这样说并非说您就须要为每一个文档中的每一条信息建立副本。与此相反,与其在设计的时候被复制数据的担心牵着走,还不如按照不一样的信息应该归属于相应的文档这一思路来对数据建模。

好比说,假设您在编写一个论坛的应用程序。把一个 user 和一篇 post 关联起来的传统方法是在 posts 中加入一个 userid 的列。这样的模型中,若是要显示 posts 就不得不读取(链接)users。一种简单可行的替代方案就是直接把 nameuserid 存储在 post 中。您甚至能够用嵌入文档来实现,好比说 user: {id: ObjectId('Something'), name: 'Leto'}。固然,若是容许用户更改他们的用户名,那么每当有用户名修改的时候,您就须要去更新全部的文档了(这须要一个额外的查询)。

对一些人来讲改用这种方法并不是易事。甚至在一些状况下根本行不通。不过别不敢去尝试这种方法:有时候它不只可行,并且就是正确的方法。

4.1.4 应该选择哪种?

当处理一对多或是多对多问题的时候,采用id数组每每都是正确的策略。能够这么说,DBRef 并非那么经常使用,虽然您彻底能够试着采用这项技术。这使得新手们在面临选择嵌入文档仍是手工引用(manual reference)时犹豫不决。

首先,要知道目前一个单独的文档的大小限制是 4MB,虽然已经比较大了。了解了这个限制能够为如何使用文档提供一些思路。目前看来多数的开发者仍是大量地依赖手工引用来维护数据的关系。嵌入文档常常被使用,but mostly for small pieces of data which we want to always pull with the parent document。一个真实的例子,我把 accounts 文档嵌入存储在用户的文档中,就像这样:

db.users.insert({name: 'leto', email: 'leto@dune.gov', account: {allowed_gholas: 5, spice_ration: 10}})

这不是说您就应该低估嵌入文档的做用,也不是说应该把它当成是鲜少用到的工具并直接忽略。将数据模型直接映射到目标对象上可使问题变得更加简单,也每每所以而再也不须要链接操做。当您知道 MongoDB 容许对嵌入文档的域进行查询并作索引后,这个说法就尤为显得正确了。

4.2 集合:少一些仍是多一些?

既然集合不强制使用模式,那么就彻底有可能用一个单一的集合以及一个不匹配的文档构建一个系统。以我所见过的状况,大部分的 MongoDB 系统都像您在关系数据库中所见到的那样布局。换句话说,若是在关系数据库中会用表,那么颇有可能在 MongoDB 中就要用集合(多对多链接表在这里是一个不可忽视的例外)

当把嵌入文档引进来的时候,讨论就会变得更加有意思了。最多见的例子就是博客系统。是应该分别维护 postscomments 两个集合,仍是在每一个 post 中嵌入一个 comments 数组?暂且不考虑那个 4MB 的限制(哈姆雷特全部的评论也不超过200KB,谁的博客会比他更受欢迎?),大多数的开发者仍是倾向于把数据划分开。由于这样既简洁又明确。

没有什么硬性的规定(呃,除了 4MB 的限制)。作了不一样的尝试以后您就能够凭感受知道怎样作是对的了。

总结

至此已经对 MongoDB 有了一个基本的了解和入门,可是要运用在实际的项目中仍然有许多实践须要本身去完成


按照惯例黏一个尾巴:

欢迎转载,转载请注明出处!
简书ID:@我没有三颗心脏
github:wmyskxz 欢迎关注公众微信号:wmyskxz 分享本身的学习 & 学习资料 & 生活 想要交流的朋友也能够加qq群:3382693

相关文章
相关标签/搜索