传统的计算机应用大多使用关系型数据库来存储数据,好比你们可能熟悉的MySql, Sqlite等等,它的特色是数据以表格(table)的形式储存起来的。数据库由一张张排列整齐的表格构成,就好像一个Excel表单同样,每一个表格会有若干列,好比一个学生信息表,可能包含学号、姓名、性别、入学年份、高考成绩、籍贯等等。而表格的每一排,则是一个个学生的具体信息。在企业级应用和前互联网时代,关系型数据库几乎是不二选择。关系型数据库的特色是有整齐划一的组织,很方便对数据进行描述、插入、搜索。git
想象有一个传统的网上服装商店吧,它的主要的数据多是储存在一张叫products的表单里,表单可能包含这些列:商品编号(ID)、名称(Name)、商家(brand)、主目录(cate)、子目录(sub-cat)、零售价(price)、是否促销(promotion)等等。若是有一个用户想要查找全部价格低于300元的正在促销的鞋子的编号和名称,则能够执行相似于如下的SQL语句:github
SELECT ID, name FROM products WHERE cate='shoes' AND price<300 and AND promotion=true;
SQL具有了强大了的深度查询能力,能知足各式各样的查询要求。而若是要对数据进行添加和删除,成本也是很是低的。这些是SQL的优点之一, 但随着互联网的兴起以及数据形式的多样化,四平八稳的SQL表单在一些领域渐渐显现出它的劣势。让咱们经过一个例子来讲明。考虑一个博客后台系统,若是咱们用关系型数据库为每篇博客(article)建一个表单的话,这个表单大概会包括如下这些列:web
ID | Title | Description | Author | Content | Likes |
---|---|---|---|---|---|
A_1 | Title1 | Political Article | Joe | Content 1 | 12 |
A_2 | Title2 | Humorous Story | Sam | Content 2 | 50 |
这时候用SQL数据库来存储是很是方便的,但假如咱们要位每篇文章添加评论功能,会发现每篇文章可能要多篇评论,并且这个数目是动态变化的,并且每篇评论还包括好几项内容:评论的人、评论的时间、以及评论内容。这时候要将这些内容都塞进上述的那个表,就显得很困难。一般的作法是为评论(comment)单独建一个表:正则表达式
ID | Author | Time | Content | Article |
---|---|---|---|---|
C_1 | Anna | 2014-12-26 08:23 | Really good articles! | A_1 |
C_2 | David | 2014-12-25 09:30 | I like it! | A_1 |
相似地,每篇文章可能会有若干标签(tags)。标签自己又是一个表单:mongodb
ID | Category | Tags | Content | Article |
---|---|---|---|---|
T_1 | Anna | 2014-12-26 08:23 | Really good articles! | A_1 |
T_2 | David | 2014-12-25 09:30 | I like it! | A_2 |
而博客的表格则要经过foreign key跟这些相关联的表格联系起来(可能还包括做者、出版社等其它表格)。这样一来,当咱们作查询的时候,好比说,“找出评论数很多于3的标签为‘政治评论’的做者为Sam的文章”,就会涉及到复杂的跨表查询,须要大量使用join
语句。这种跨表查询不只下降了查询速度,并且这些语句写起来也不简单。数据库
那么,若是用MongoDB数据库来实现,能够如何设计数据模型呢?很简单,像下面这样:express
_id: POST_ID title: TITLE_OF_POST, description: POST_DESCRIPTION, author: POST_BY, tags: [TAG1, TAG2, TAG3], likes: TOTAL_LIKES, comments: [ { user:'COMMENT_BY', message: TEXT, dateCreated: DATE_TIME, }, { user:'COMMENT_BY', message: TEXT, dateCreated: DATE_TIME, } ]
在MongoDB里,每篇博客文章以一个文档(document)的形式保存起来,而文档内部包含了不少项目,好比title tags
等,每个项目都是key-value
的形式,即有一个项目的名字,好比title
,以及它的值TITLE_OF_POST
。而重要的是,一个key
能够有多个values
,他们用[]
括起来。学习
这种“宽松”的数据存储形式很是灵活,MongoDB不限制每一个key
对应的values
的数目。好比有的文章没有评论,则它的值就是一个空集,彻底没有问题;有的文章评论不少,也能够无限制地插入。更灵活的是,MongoDB不要求同一个集合(collection,至关于SQL的table)里面的不一样document有相同的key,好比除了上述这种文档组织,有的文档所表明的文章可能没有likes这个项目,再好比有的文章可能有更多的项目,好比可能还有dislikes等等。这些不一样的文档均可以灵活地存储在同一个集合下,并且查询起来也异常简单,由于都在一个文档里,不用进行各类跨文档查询。而这种MongoDB式的存储也方便了数据的维护,对于一篇博客文章来讲,全部的相关数据都在这个document里面,不用去考虑一个数据操做须要involve多少个表格。测试
固然,除了上述的优势,MongoDB还有很多别的优点,好比MongoDB的数据是用JSON(Javascript Object Notation)存储的(就是上面的这种key-value的形式),而几乎全部的web应用都是基于Javascript的。所以,存储的数据和应用的数据的格式是高度一致的,不需通过转换。更多的优势能够查看:[2]。网站
这个极简教程,或者说笔记,并非一个覆盖MongoDB方方面面的教程。所谓极简的意思,就是只选取那些最重要、最经常使用的内容进行基于实例的介绍,从而让读者可以在最短的时间内快速上手,而且能顺利地进行后续的纵深的学习。
具体地说,这个教程的特色是:
阅读这篇文章不须要有特别的基础,但最好知道数据库的基本概念,若是自己熟悉SQL那就更好啦。
MongoDB能够在Windows、Linux、Mac OS X等主流平台运行,并且下载和安装很是简单,很是友好。这篇文档的例子采用MongoDB 2.6版本,均在OS X测试过,有充足的理由相信,在其它平台也能顺利运行。
在上一节执行完步骤6后,你会看到命令行里显示:`connecting to: test`,这里的`test`是默认的数据库。这里咱们能够新建一个数据库。在命令行里打入:
use tutorial
这样就新建了一个叫作tutorial
的数据库。你能够执行
show databases
来显示当前的数据库。不过这时候因为咱们的新数据库是空的,因此会显示相似这样的:
admin (empty) local 0.078GB
咱们试着往咱们的数据库里添加一个集合(collection),MongoDB里的集合和SQL里面的表格是相似的:
db.createCollection('author')
顺利的话会显示:
{ "ok" : 1 }
表示建立成功。
你能够再回头执行:
show databases
这时候咱们的tutorial集合已经位列其中。你能够再执行
show collections
能够看到建立的集合author也在其中。
咱们暂时不须要author这个集合,因此咱们能够经过执行:
db.author.drop()
来将其删除。这时候你再执行show collections
,就再也看不到咱们的author了。
这一节要记住的点主要只有一个:集合(collection)相似于SQL的表格(table),相似于Excel的一个个表格。
想象一个精简版的“豆瓣电影”。咱们须要建立一个数据库,来存储每部电影的信息,电影的信息包括:
显然咱们须要先建立一个叫电影的集合:
db.createCollection('movie')
而后,咱们就能够插入数据了:
db.movie.insert( { title: 'Forrest Gump', directed_by: 'Robert Zemeckis', stars: ['Tom Hanks', 'Robin Wright', 'Gary Sinise'], tags: ['drama', 'romance'], debut: new Date(1994,7,6,0,0), likes: 864367, dislikes: 30127, comments: [ { user:'user1', message: 'My first comment', dateCreated: new Date(2013,11,10,2,35), like: 0 }, { user:'user2', message: 'My first comment too!', dateCreated: new Date(2013,11,11,6,20), like: 0 } ] } )
请注意,这里插入数据以前,咱们并不须要先声明movie这个集合里面有哪些项目。咱们直接插入就能够了~这一点和SQL不同,SQL必须先声明一个table里面有哪些列,而MongoDB不须要。
把上面的例子复制进命令行应该能够顺利运行,但我强烈建议你手动打一下,或者输入一部你本身喜欢的电影。insert
操做有几点须要注意:
若是你在insert
以后看到WriteResult({ "nInserted" : 1 })
,说明写入成功。
这个时候你能够用查询的方式来返回数据库中的数据:
db.movie.find().pretty()
这里find()
里面是空的,说明咱们不作限制和筛选,相似于SQL没有WHERE
语句同样。而pretty()
输出的是经格式美化后的数据,你能够本身试试没有pretty()
会怎么样。
仔细观察find()
的结果,你会发现多了一个叫'_id'
的东西,这是数据库自动建立的一个ID号,在同一个数据库里,每一个文档的ID号都是不一样的。
咱们也能够同时输入多个数据:
db.movie.insert([ { title: 'Fight Club', directed_by: 'David Fincher', stars: ['Brad Pitt', 'Edward Norton', 'Helena Bonham Carter'], tags: 'drama', debut: new Date(1999,10,15,0,0), likes: 224360, dislikes: 40127, comments: [ { user:'user3', message: 'My first comment', dateCreated: new Date(2008,09,13,2,35), like: 0 }, { user:'user2', message: 'My first comment too!', dateCreated: new Date(2003,10,11,6,20), like: 14 }, { user:'user7', message: 'Good Movie!', dateCreated: new Date(2009,10,11,6,20), like: 2 } ] }, { title: 'Seven', directed_by: 'David Fincher', stars: ['Morgan Freeman', 'Brad Pitt', 'Kevin Spacey'], tags: ['drama','mystery','thiller'], debut: new Date(1995,9,22,0,0), likes: 134370, dislikes: 1037, comments: [ { user:'user3', message: 'Love Kevin Spacey', dateCreated: new Date(2002,09,13,2,35), like: 0 }, { user:'user2', message: 'Good works!', dateCreated: new Date(2013,10,21,6,20), like: 14 }, { user:'user7', message: 'Good Movie!', dateCreated: new Date(2009,10,11,6,20), like: 2 } ] } ])
顺利的话会显示:
BulkWriteResult({ "writeErrors" : [ ], "writeConcernErrors" : [ ], "nInserted" : 2, "nUpserted" : 0, "nMatched" : 0, "nModified" : 0, "nRemoved" : 0, "upserted" : [ ]
表面咱们成功地插入了两个数据。注意批量插入的格式是这样的:db.movie.insert([{ITEM1},{ITEM2}])
。几部电影的外面须要用[]括起来。
请注意,虽然collection的插入不须要先声明,但表达相赞成思的key,名字要同样,好比,若是咱们在一个文档里用directed_by
来表示导演,则在其它文档也要保持一样的名字(而不是director
之类的)。不一样的名字不是不能够,技术上彻底可行,但会给查询和更新带来困难。
好了,到这里,咱们就有了一个叫tutorial的数据库,里面有一个叫movie的集合,而movie里面有三个记录。接下来咱们就能够对其进行查询了。
在上一节咱们已经接触到最简单的查询db.movie.find().pretty()
。MongoDB支持各类各样的深度查询功能。先来一个最简单的例子,找出大卫芬奇(David Fincher)导演的全部电影:
db.movie.find({'directed_by':'David Fincher'}).pretty()
将返回《搏击俱乐部》和《七宗罪》两部电影。这种搜索和SQL的WHERE
语句是很类似的。
也能够设置多个条件。好比找出大卫芬奇导演的, 摩根弗里曼主演的电影:
db.movie.find({'directed_by':'David Fincher', 'stars':'Morgan Freeman'}).pretty()
这里两个条件之间,是AND的关系,只有同时知足两个条件的电影才会被输出。同理,能够设置多个的条件,不赘述。
条件之间也能够是或的关系,好比找出罗宾怀特或摩根弗里曼主演的电影:
db.movie.find( { $or: [ {'stars':'Robin Wright'}, {'stars':'Morgan Freeman'} ] }).pretty()
注意这里面稍显复杂的各类括号。
还能够设置一个范围的搜索,好比找出50万人以上赞的电影:
db.movie.find({'likes':{$gt:500000}}).pretty()
一样要注意略复杂的括号。注意,在这些查询里,key的单引号都是可选的,也就是说,上述语句也能够写成:
db.movie.find({likes:{$gt:500000}}).pretty()
相似地,少于二十万人赞的电影:
db.movie.find({likes:{$lt:200000}}).pretty()
相似的运算符还有:$let
:小于或等于;$get
:大于或等于;$ne
:不等于。
注意,对于包含多个值的key,一样能够用find来查询。好比:
db.movie.find({'tags':'romance'})
将返回《阿甘正传》,虽然其标签既有romance,又有drama,但只要符合一个就能够了。
若是你确切地知道返回的结果只有一个,也能够用findOne
:
db.movie.findOne({'title':'Forrest Gump'})
若是有多个结果,则会按磁盘存储顺序返回第一个。请注意,findOne()
自带pretty模式,因此不能再加pretty()
,将报错。
若是结果不少而你只想显示其中一部分,能够用limit()
和skip()
,前者指明输出的个数,后者指明从第二个结果开始数。好比:
db.movie.find().limit(2).skip(1).pretty()
则跳过第一部,从第二部开始选取两部电影。
第五节的时候咱们讲了find
的用法,但对于符合条件的条目,咱们都是返回整个JSON文件的。这相似于SQL里面的SELECT *
。有的时候,咱们须要的,仅仅是部分数据,这个时候,find
的局部查询的功能就派上用场了。先来看一个例子,返回tags为drama的电影的名字和首映日期。
db.movie.find({'tags':'drama'},{'debut':1,'title':1}).pretty()
数据库将返回:
{ "_id" : ObjectId("549cfb42f685c085f1dd47d4"), "title" : "Forrest Gump", "debut" : ISODate("1994-08-05T16:00:00Z") } { "_id" : ObjectId("549cff96f685c085f1dd47d6"), "title" : "Fight Club", "debut" : ISODate("1999-11-14T16:00:00Z") } { "_id" : ObjectId("549cff96f685c085f1dd47d7"), "title" : "Seven", "debut" : ISODate("1995-10-21T16:00:00Z") }
这里find的第二个参数是用来控制输出的,1表示要返回,而0则表示不返回。默认值是0,但_id
是例外,所以若是你不想输出_id
,须要显式地声明:
db.movie.find({'tags':'drama'},{'debut':1,'title':1,'_id':0}).pretty()
不少状况下你须要更新你的数据库,好比有人对某部电影点了个赞,那么你须要更新相应的数据库。好比有人对《七宗罪》点了个赞,而它原本的赞的个数是134370,那么你须要更新到134371。能够这样操做:
db.movie.update({title:'Seven'}, {$set:{likes:134371}})
第一个大括号里代表要选取的对象,第二个代表要改动的数据。请注意上述的操做至关不现实,由于你首先要知道以前的数字是多少,而后加一,但一般你不读取数据库的话,是不会知道这个数(134370)的。MongoDB提供了一种简便的方法,能够对现有条目进行增量操做。假设又有人对《七宗罪》点了两个赞,则能够:
db.movie.update({title:'Seven'}, {$inc:{likes:2}})
若是你查询的话,会发现点赞数变为134373了,这里用的是$inc
。除了增量更新,MongoDB还提供了不少灵活的更新选项,具体能够看:http://docs.mongodb.org/manual/reference/operator/update-field/ 。
注意若是有多部符合要求的电影。则默认只会更新第一个。若是要多个同时更新,要设置{multi:true}
,像下面这样:
db.movie.update({}, {$inc:{likes:10}},{multi:true})
全部电影的赞数都多了10.
注意,以上的更新操做会替换掉原来的值,因此若是你是想在原有的值得基础上增长一个值的话,则应该用$push
,好比,为《七宗罪》添加一个popular的tags。
db.movie.update({'title':'Seven'}, {$push:{'tags':'popular'}})
你会发现《七宗罪》如今有四个标签:
"tags" : [ "drama", "mystery", "thiller", "popular" ],
删除的句法和find很类似,好比,要删除标签为romance的电影,则:
db.movie.remove({'tags':'romance'})
考虑到咱们数据库条目异常稀少,就不建议你执行这条命令了~
注意,上面的例子会删除全部标签包含romance的电影。若是你只想删除第一个,则
db.movie.remove({'tags':'romance'},1)
若是不加任何限制:
db.movie.remove()
会删除movie这个集合下的全部文档。
为文档中的一些key加上索引(index)能够加快搜索速度。这一点不难理解,假如没有没有索引,咱们要查找名字为Seven的电影,就必须在全部文档里逐个搜索。而若是对名字这个key加上索引值,则电影名这个字符串和数字创建了映射,这样在搜索的时候就会快不少。排序的时候也是如此,不赘述。MongoDB里面为某个key加上索引的方式很简单,好比咱们要对导演这个key加索引,则能够:
db.movie.ensureIndex({directed_by:1})
这里的1是升序索引,若是要降序索引,用-1。
MongoDB支持对输出进行排序,好比按名字排序:
db.movie.find().sort({'title':1}).pretty()
一样地,1是升序,-1是降序。默认是1。
db.movie.getIndexes()
将返回全部索引,包括其名字。
而
db.movie.dropIndex('index_name')
将删除对应的索引。
MongoDB支持相似于SQL里面的GROUP BY
操做。好比当有一张学生成绩的明细表时,咱们能够找出每一个分数段的学生各有多少。为了实现这个操做,咱们须要稍加改动咱们的数据库。执行如下三条命令:
db.movie.update({title:'Seven'},{$set:{grade:1}}) db.movie.update({title:'Forrest Gump'},{$set:{grade:1}}) db.movie.update({title:'Fight Club'},{$set:{grade:2}})
这几条是给每部电影加一个虚拟的分级,前两部是归类是一级,后一部是二级。
这里你也能够看到MongoDB的强大之处:能够动态地后续添加各类新项目。
咱们先经过聚合来找出总共有几种级别。
db.movie.aggregate([{$group:{_id:'$grade'}}])
输出:
{ "_id" : 2 } { "_id" : 1 }
注意这里的2和1是指级别,而不是每一个级别的电影数。这个例子看得清楚些:
db.movie.aggregate([{$group:{_id:'$directed_by'}}])
这里按照导演名字进行聚合。输出:
{ "_id" : "David Fincher" } { "_id" : "Robert Zemeckis" }
接着咱们要找出,每一个导演的电影数分别有多少:
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$sum:1}}}])
将会输出:
{ "_id" : "David Fincher", "num_movie" : 2 } { "_id" : "Robert Zemeckis", "num_movie" : 1 }
注意$sum后面的1表示只是把电影数加起来,但咱们也能够统计别的数据,好比两位导演谁的赞比较多:
db.movie.aggregate([{$group:{_id:'$directed_by',num_likes:{$sum:'$likes'}}}])
输出:
{ "_id" : "David Fincher", "num_likes" : 358753 } { "_id" : "Robert Zemeckis", "num_likes" : 864377 }
注意这些数据都纯属虚构啊!
除了$sum
,还有其它一些操做。好比:
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$avg:'$likes'}}}])
统计平均的赞。
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$first:'$likes'}}}]
返回每一个导演的电影中的第一部的赞数。
其它各类操做能够参考:http://docs.mongodb.org/manual/reference/operator/aggregation/group/ 。
MongoDB支持单个文档内的原子化操做(atomic operation),这是说,能够将多条关于同一个文档的指令放到一块儿,他们要么一块儿执行,要么都不执行。而不会执行到一半。有些场合须要确保多条执行一块儿顺次执行。好比一个场景:一个电商网站,用户查询某种商品的剩余数量,以及用户购买该种商品,这两个操做,必须放在一块儿执行。否则的话,假定咱们先执行剩余数量的查询,这是假定为1,用户接着购买,但假如这两个操做之间还加入了其它操做,好比另外一个用户抢先购买了,那么原先购买用户的购买的行为就会形成数据库的错误,由于实际上这种商品以及没有存货了。但由于查询剩余数量和购买不是在一个“原子化操做”以内,所以会发生这样的错误[2]。
MongoDB提供了findAndModify
的方法来确保atomic operation。好比这样的:
db.movie.findAndModify( { query:{'title':'Forrest Gump'}, update:{$inc:{likes:10}} } )
query是查找出匹配的文档,和find是同样的,而update则是更新likes这个项目。注意因为MongoDB只支持单个文档的atomic operation,所以若是query出多于一个文档,则只会对第一个文档进行操做。
findAndModify
还支持更多的操做,具体见:http://docs.mongodb.org/manual/reference/command/findAndModify/。
除了前面介绍的各类深度查询功能,MongoDB还支持文本搜索。对文本搜索以前,咱们须要先对要搜索的key创建一个text索引。假定咱们要对标题进行文本搜索,咱们能够先这样:
db.movie.ensureIndex({title:'text'})
接着咱们就能够对标题进行文本搜索了,好比,查找带有"Gump"的标题:
db.movie.find({$text:{$search:"Gump"}}).pretty()
注意text和search前面的$符号。
这个例子里,文本搜索做用不是很是明显。但假设咱们要搜索的key是一个长长的文档,这种text search的方便性就显现出来了。MongoDB目前支持15种语言的文本搜索。
MongoDB还支持基于正则表达式的查询。若是不知道正则表达式是什么,能够参考Wikipedia。这里简单举几个例子。好比,查找标题以b
结尾的电影信息:
db.movie.find({title:{$regex:'.*b$'}}).pretty()
也能够写成:
db.movie.find({title:/.*b$/}).pretty()
查找含有'Fight'标题的电影:
db.movie.find({title:/Fight/}).pretty()
注意以上匹配都是区分大小写的,若是你要让其不区分大小写,则能够:
db.movie.find({title:{$regex:'fight.*b',$options:'$i'}}).pretty()
$i
是insensitive的意思。这样的话,即便是小写的fight,也能搜到了。
至此,MongoDB的最基本的内容就介绍得差很少了。若是有什么遗漏的之后我会补上來。若是你一路看到底彻底了这个入门教程,恭喜你,你必定是一个有毅力的人。
把这个文档过一遍,不会让你变成一个MongoDB的专家(若是会那就太奇怪了)。但若是它能或多或少减小你上手的时间,或者让你意识到“咦,MongoDB其实没那么复杂”,那么这个教程的目的也就达到啦。
这个文档是匆忙写就的,出错简直是必定的。若是您发现了任何错误或者有关于本文的任何建议,麻烦发邮件给我(stevenslxie at gmail.com)或者在GitHub上直接交流,不胜感激。