0. 什么是MongoDB?
MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。
MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。
MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
1. 为什么用MongoDB?
现代的计算机应用大多使用关系型数据库来存储数据(如MySql, Sqlite等等),它的特点是数据以表格(table)的形式储存起来的。
数据库由一张张排列整齐的表格构成,就好像一个Excel表单一样,每个表格会有若干列。比如一个学生信息表:可能包含学号、姓名、性别、入学年份、高考成绩、籍贯等等。而表格的每一排,则是一个个学生的具体信息。
在企业级应用和前互联网时代,关系型数据库几乎是不二选择。关系型数据库的特点是有整齐划一的组织,很方便对数据进行描述、插入、搜索。
想象有一个网上服装商店,它的主要的数据可能是储存在一张叫products的表单里。
表单可能包含这些列:商品编号(ID)、名称(Name)、商家(brand)、主目录(cate)、子目录(sub-cat)、零售价(price)、是否促销(promotion)等等。如果有用户想要查找所有价格低于250元的正在促销的鞋子的编号和名称,则可以执行类似于以下的SQL语句:
SELECT ID, name FROM products WHERE cate='shoes' AND price<250 and AND promotion=true;
SQL具备了强大了的深度查询能力,能满足各式各样的查询要求。而如果要对数据进行添加和删除,成本也是非常低的。这些是SQL的优势之一, 但随着互联网的兴起以及数据形式的多样化,四平八稳的SQL表单在一些领域渐渐显现出它的劣势。
让我们通过一个例子来说明。考虑一个博客后台系统,如果我们用关系型数据库为每篇博客(article)建一个表单的话,这个表单大概会包括以下这些列:
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 | ArticleId |
---|---|---|---|---|
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)。标签本身又是一个表单:
ID | Category | Tags | Content | ArticleId |
---|---|---|---|---|
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数据库来实现,可以如何设计数据模型呢?很简单,像下面这样 MongoDb数据模型:
{
_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的。因此,存储的数据和应用的数据的格式是高度一致的,不需经过转换。更多的优点可以查看 MongoDb 优点
2. 安装MongoDb和连接数据库
请参考链接 Windows平台 , Linux平台 和 连接数据库本文在此不多做介绍了。
3. 入门学习
(0)MongoDB概念解析
不管我们学习什么数据库都应该学习其中的基础概念,在mongodb中基本的概念是文档、集合、数据库。
下表将帮助您更容易理解Mongo中的一些概念:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
(1)MongoDB 数据库
- 创建数据库
语法 : use DATABASE_NAME
如果数据库不存在,则创建数据库,否则切换到指定数据库。
实例
以下实例我们创建了数据库 runoob:
> use runoob
switched to db runoob
> db
runoob
>
如果你想查看所有数据库,可以使用 show dbs 命令:
> show dbs
local 0.078GB
test 0.078GB
>
可以看到,我们刚创建的数据库 runoob 并不在数据库的列表中, 要显示它,我们需要向 runoob 数据库插入一些数据。
> db.runoob.insert({"name":"菜鸟教程"})
WriteResult({ "nInserted" : 1 })
> show dbs
local 0.078GB
runoob 0.078GB
test 0.078GB
>
MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。
- 删除数据库
语法: db.dropDatabase()
实例
以下实例我们删除了数据库 runoob。
首先,查看所有数据库:
> show dbs
local 0.078GB
runoob 0.078GB
test 0.078GB
接下来我们切换到数据库 runoob:
> use runoob
switched to db runoob
>
执行删除命令:
> db.dropDatabase()
{ "dropped" : "runoob", "ok" : 1 }
最后,我们再通过 show dbs 命令数据库是否删除成功:
> show dbs
local 0.078GB
test 0.078GB
>
删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。
(2)集合
- 创建集合
语法:db.createCollection(name, options)
参数说明:
name: 要创建的集合名称
options: 可选参数, 指定有关内存大小及索引的选项
实例
在 test 数据库中创建 runoob 集合:
> use test
switched to db test
> db.createCollection("runoob")
{ "ok" : 1 }
>
如果要查看已有集合,可以使用 show collections 命令:
> show collections
runoob
下面是带有几个关键参数的 createCollection() 的用法:
创建固定集合 mycol,整个集合空间大小 6142800 KB, 文档最大个数为 10000 个。
> db.createCollection("mycol", { capped : true, autoIndexId : true, size :
6142800, max : 10000 } )
{ "ok" : 1 }
>
在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。
> db.mycol2.insert({"name" : "菜鸟教程"})
> show collections
mycol2
- 删除集合
语法:db.collection.drop()
返回值
如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。
实例
在数据库 mydb 中,我们可以先通过 show collections 命令查看已存在的集合:
>use mydb
switched to db mydb
>show collections
mycol
mycol2
system.indexes
runoob
>
接着删除集合 mycol2 :
>db.mycol2.drop()
true
>
通过 show collections 再次查看数据库 mydb 中的集合:
>show collections
mycol
system.indexes
runoob
>
从结果中可以看出 mycol2 集合已被删除。
(3)文档
1.插入文档
语法:db.COLLECTION_NAME.insert(document)
实例
以下文档可以存储在 MongoDB 的 runoob 数据库 的 col 集合中:
>db.col.insert({title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: '菜鸟教程',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
})
以上实例中 col 是我们的集合名,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。
查看已插入文档:
> db.col.find()
{ "_id" : ObjectId("56064886ade2f21f36b03134"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }
>
我们也可以将数据定义为一个变量,如下所示:
> document=({title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: '菜鸟教程',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
});
执行后显示结果如下:
{
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
执行插入操作:
> db.col.insert(document)
WriteResult({ "nInserted" : 1 })
>
插入文档你也可以使用 db.col.save(document) 命令。如果不指定 _id 字段 save() 方法类似于 insert() 方法。如果指定 _id 字段,则会更新该 _id 的数据
3.2 版本后还有以下几种语法可用于插入文档:
db.collection.insertOne():向指定集合中插入一条文档数据
db.collection.insertMany():向指定集合中插入多条文档数据
2.更新文档
update方法
update() 方法用于更新已存在的文档。语法格式如下:
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
实例
我们在集合 col 中插入如下数据:
>db.col.insert({
title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: '菜鸟教程',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
})
接着我们通过 update() 方法来更新标题(title):
>db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) # 输出信息
> db.col.find().pretty()
{
"_id" : ObjectId("56064f89ade2f21f36b03136"),
"title" : "MongoDB",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
>
可以看到标题(title)由原来的 "MongoDB 教程" 更新为了 "MongoDB"。
以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,则需要设置 multi 参数为 true。
>db.col.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})
save() 方法
save() 方法通过传入的文档来替换已有文档。语法格式如下:
db.collection.save(
<document>,
{
writeConcern: <document>
}
)
参数说明:
document : 文档数据。
writeConcern :可选,抛出异常的级别。
实例
以下实例中我们替换了 _id 为 56064f89ade2f21f36b03136 的文档数据:
>db.col.save({
"_id" : ObjectId("56064f89ade2f21f36b03136"),
"title" : "MongoDB",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "Runoob",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"NoSQL"
],
"likes" : 110
})
替换成功后,我们可以通过 find() 命令来查看替换后的数据
>db.col.find().pretty()
{
"_id" : ObjectId("56064f89ade2f21f36b03136"),
"title" : "MongoDB",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "Runoob",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"NoSQL"
]
"likes" : 110
}
>
3.删除文档
remove 方法
实例
以下文档我们执行两次插入操作:
>db.col.insert({title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: '菜鸟教程',
url: 'http://www.runoob.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
})
使用 find() 函数查询数据:
> db.col.find()
{ "_id" : ObjectId("56066169ade2f21f36b03137"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }
{ "_id" : ObjectId("5606616dade2f21f36b03138"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb", "database", "NoSQL" ], "likes" : 100 }
接下来我们移除 title 为 'MongoDB 教程' 的文档:
>db.col.remove({'title':'MongoDB 教程'})
WriteResult({ "nRemoved" : 2 }) # 删除了两条数据
>db.col.find()
…… # 没有数据
如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示:
>db.COLLECTION_NAME.remove(DELETION_CRITERIA,1)
如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令):
>db.col.remove({})
>db.col.find()
>
推荐使用 deleteOne() 和 deleteMany() 方法。
如删除集合下全部文档:
db.inventory.deleteMany({})
删除 status 等于 A 的全部文档:
db.inventory.deleteMany({ status : "A" })
删除 status 等于 D 的一个文档:
db.inventory.deleteOne( { status: "D" } )
4.查询文档.
find() 方法
find() 方法以非结构化的方式来显示所有文档。
MongoDB 查询数据的语法格式如下:
db.collection.find(query, projection)
query :可选,使用查询操作符指定查询条件
projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:
>db.col.find().pretty()
pretty() 方法以格式化的方式来显示所有文档。
实例
以下实例我们查询了集合 col 中的数据:
> db.col.find().pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。
MongoDB 与 RDBMS Where 语句比较
如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件语句查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 |
{<key>:<value> } |
db.col.find({"by":"菜鸟教程"}).pretty() |
where by = '菜鸟教程' |
小于 | {<key>:{$lt:<value>}} |
db.col.find({"likes":{$lt:50}}).pretty() |
where likes < 50 |
小于或等于 | {<key>:{$lte:<value>}} |
db.col.find({"likes":{$lte:50}}).pretty() |
where likes <= 50 |
大于 | {<key>:{$gt:<value>}} |
db.col.find({"likes":{$gt:50}}).pretty() |
where likes > 50 |
大于或等于 | {<key>:{$gte:<value>}} |
db.col.find({"likes":{$gte:50}}).pretty() |
where likes >= 50 |
不等于 | {<key>:{$ne:<value>}} |
db.col.find({"likes":{$ne:50}}).pretty() |
where likes != 50 |
MongoDB AND 条件
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。
语法格式如下:
>db.col.find({key1:value1, key2:value2}).pretty()
实例
以下实例通过 by 和 title 键来查询 菜鸟教程 中 MongoDB 教程 的数据
> db.col.find({"by":"菜鸟教程", "title":"MongoDB 教程"}).pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
以上实例中类似于 WHERE 语句:WHERE by='菜鸟教程' AND title='MongoDB 教程'
MongoDB OR 条件
MongoDB OR 条件语句使用了关键字 $or,语法格式如下:
>db.col.find(
{
$or: [
{key1: value1}, {key2:value2}
]
}
).pretty()
实例
以下实例中,我们演示了查询键 by 值为 菜鸟教程 或键 title 值为 MongoDB 教程 的文档。
>db.col.find({$or:[{"by":"菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
>
AND 和 OR 联合使用
以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: 'where likes>50 AND (by = '菜鸟教程' OR title = 'MongoDB 教程')'
>db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "菜鸟教程",
"url" : "http://www.runoob.com",
"tags" : [
"mongodb",
"database",
"NoSQL"
],
"likes" : 100
}
(4) 管道
管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
这里我们介绍一下聚合框架中常用的几个操作:
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
$match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$geoNear:输出接近某一地理位置的有序文档。
管道操作符实例
1、$project实例
db.article.aggregate(
{ $project : {
title : 1 ,
author : 1 ,
}}
);
这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样:
db.article.aggregate(
{ $project : {
_id : 0 ,
title : 1 ,
author : 1
}});
2.$match实例
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );
$match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。
3.$skip实例
db.article.aggregate(
{ $skip : 5 });
经过$skip管道操作符处理后,前五个文档被"过滤"掉。
涉及mongoDB的知识有很多,本文只是简短介绍了其中的一部分,更多资料请参考 MongoDb 文档)