MongoDB是一种强大、灵活、可扩展的数据存储形式。他是面向文档的数据库,是NoSQL的一种。它扩展了关系型数据库的众多有用功能,如辅助索引、范围查询、和排序。还内置了对MapReduce式聚合的支持,以及对地理空间索引的支持。由于它放弃了关系型数据库“行”的概念,获得了更加方便的扩展性。面向文档的方式可以将文档或数组内嵌进来,所以用一条记录就可以表示很复杂的层次关系。
MongoDB没有模式,文档的键不会事先定义也不会固定不变。
MongoDB从设计之初,就考虑了扩展的问题。他所采用的面向文档的数据模型使之可以自动在多台服务器之间分割数据。它还可以平衡集群的数据和负载,自动重排文档。如果需要更大的容量时,只需要向集群中增加新机器,数据库就会自动帮我们处理剩下的事情。
文档是MongoDB的核心概念。多个键及其相关联的值有序的放在一起就是文档。MongoDB不但饭区分类型,还区分大小写。
集合collection就是一组文档。MongoDB中的文档有点类似关系型数据库中的行,集合有点类似于关系型数据库中的表。但是与关系型数据库不同的是,MongoDB的集合是没有模式的。但是在同一个集合中使用多种模式,不管对于开发者还是管理者,都是一个噩梦。所以我们会使用多个集合。将同种类型的文档放在同一个集合,数据会更加集中,可以节省查询时间,创建索引时也可以变得十分高效。多个集合可以使我们的查询更加便捷,我们不需要从所有的数据中找我们需要的,只需要去相应的集合中查询就可以了。
常用指令
1.创建数据库
MongoDB不需要专门的创建数据库语句,直接使用use DataBaseName命令,如果数据库存在,则使用该数据库。如果该数据库不存在,则新建该数据库。
> use newdb
switched to db newdb
在 MongoDB 中默认数据库是:test
2.删除数据库
使用db.dropDatabase()命令可以删除数据库。如果没有选择任何数据库,那么它将删除默认的’test‘数据库
3.查询数据库列表
>show dbs
local 0.000025GB
test 0.00002GB
4.创建Collection
先通过use DataBaseName命令选中想要创建集合的数据库,使用命令db.createCollection("collectionName")创建collection
>use newdb
switched to db newdb
>db.createCollection("mycollection")
{ "ok" : 1 }
>
4.删除集合
MongoDB 的 db.collection.drop() 用于从数据库中删除集合
5.插入文档
使用insert()命令插入到MongoDB集合中。
db.CollectionName.insert(
{
"_id":"100",
"title":"MongoDB",
"description":"Description of MongoDB",
"url":"http://www.baidu.com"
}
)
如果不在文档中指定_id,insert方法会自动为文档分配id.
一次插入多个文档,可以在insert()语句中插入多个文档。文档用逗号隔开,再使用中括号把所有文档括起来。
db.CollectionName.insert([
{
_id: 101,
title: 'MongoDB Guide',
description: 'MongoDB is no sql database',
by: 'yiibai tutorials',
url: 'http://www.yiibai.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
},
{
_id: 102,
title: 'NoSQL Database',
description: "NoSQL database doesn't have tables",
by: 'yiibai tutorials',
url: 'http://www.yiibai.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 210,
comments: [
{
user:'user1',
message: 'My first comment',
dateCreated: new Date(2017,11,10,2,35),
like: 0
}
]
},
{
_id: 104,
title: 'Python Quick Guide',
description: "Python Quick start ",
by: 'yiibai tutorials',
url: 'http://www.yiibai.com',
tags: ['Python', 'database', 'NoSQL'],
likes: 30,
comments: [
{
user:'user1',
message: 'My first comment',
dateCreated: new Date(2018,11,10,2,35),
like: 590
}
]
}
])
6.查询文档
使用db.COLLECTION_NAME.find(document)
查询文档。find()方法会找出collection中的所有文档。
带参find()方法,第一个参数用来指定查询条件,第二个参数用来表示要检索的字段列表。字段列表对应值设置为1即为显示,设置为0即为隐藏字段。
> db.mycol.find({}, {'_id':1, 'title':1})
{ "_id" : 101, "title" : "MongoDB Guide" }
{ "_id" : 102, "title" : "NoSQL Database" }
{ "_id" : 104, "title" : "Python Quick Guide" }
{ "_id" : 100, "title" : "MongoDB Overview" }
>
第一个参数可以指定查询过滤器,或类似关系型数据库指定一些操作符或查询运算符
>db.mycol.find({"likes": {$gt:10}, $or: [{"by": "yiibai tutorials"},
{"title": "MongoDB Overview"}]}).pretty() #and 和 or一起使用
{
"_id": 100,
"title": "MongoDB Overview",
"description": "MongoDB is no sql database",
"by": "yiibai tutorials",
"url": "http://www.yiibai.com",
"tags": ["mongodb", "database", "NoSQL"],
"likes": "100"
}
>
查询限制数量
使用db.COLLECTION_NAME.find().limit(NUMBER)指令可以限制查询NUMBER条
查询跳过条目
使用skip()函数跳过一定量的些条目db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)
7.更新文档
update方法更新现有文档中的值db.COLLECTION_NAME.update(SELECTION_CRITERIA, UPDATED_DATA)
>db.mycol.update({'title':'MongoDB Overview'},
{$set:{'title':'New Update MongoDB Overview'}},{multi:true})
save()方法传递新的文档数据替换现有文档
8.聚合
在关系型数据库中使用count(*)和group by组合产生聚合功能,而在MongoDB中应该使用aggregate()函数
假设我们的集合中有以下数据
db.article.insert([
{
_id: 100,
title: 'MongoDB Overview',
description: 'MongoDB is no sql database',
by_user: 'Maxsu',
url: 'http://www.yiibai.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
},
{
_id: 101,
title: 'NoSQL Overview',
description: 'No sql database is very fast',
by_user: 'Maxsu',
url: 'http://www.yiibai.com',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 10
},
{
_id: 102,
title: 'Neo4j Overview',
description: 'Neo4j is no sql database',
by_user: 'Kuber',
url: 'http://www.neo4j.com',
tags: ['neo4j', 'database', 'NoSQL'],
likes: 750
},
{
_id: 103,
title: 'MySQL Overview',
description: 'MySQL is sql database',
by_user: 'Curry',
url: 'http://www.yiibai.com/mysql/',
tags: ['MySQL', 'database', 'SQL'],
likes: 350
}])
现在从上面的集合中,如果要显示一个列表,说明每个用户写入了多少个教程,那么可使用以下aggregate函数
> db.article.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
{ "_id" : "Curry", "num_tutorial" : 1 }
{ "_id" : "Kuber", "num_tutorial" : 1 }
{ "_id" : "Maxsu", "num_tutorial" : 2 }
>
9.主从复制
主从复制是MongoDB最常用的复制的方式,可用于备份、故障恢复、读扩展等。
首先需要建立一个主节点和一个或多个从节点,每个从节点需要知道主节点的地址。运行mongod -master启动主节点。运行mongod --slave --source master_address启动从节点。
生产环境下可能会有多台服务器,我们在同一台服务器上替代实验。首先需要给主节点建立数据目录,并绑定端口号(10000):
$ mkdir -p ~/dbs/master
$ ./mongod --dbpath ~/dbs/master --port 10000 --master
接着设置从节点,需要选择不同的目录和端口,并且使用--source为从节点指明主节点的地址:
$ mkdir -p ~/dbs/slave
$ ./mongod --dbpath ~/dbs/slave --port 10001 --slave --source localhost:10000
这种方式只能从节点从主节点复制,还没有能够从从节点复制的机制,因为从节点不保存自己的oplog。
10.副本集
副本集是具有自动故障恢复的主从集群。主从集群没有固定的主节点,整个集群会推举出一个主节点,档主节点无法工作时,变更到其他的节点。即,副本集总会有一个活跃节点(primary)和一个或多个备份节点(secondary)。
现在我们来将独立的MongoDB实例转换为副本集。
首先我们需要找到机器的主机名
$ cat /etc/hostname
morton
接着我们要关机现有的MongoDB服务器,启动新的服务器
mongod --port 27017 --dbpath "D:\set up\mongodb\data" --replSet rs0
--replSet的作用是让服务器知道,这个rs0服务器还有其他的同伴还未启动。
以同样的方式启动多台。
然后在shell中,连接其中一个服务器(使用morton:10001),初始化副本集:
$ ./mongo morton:10001/admin
11.MongoDB的关联关系
MongoDB中的关系指的是各个文档之间在逻辑上的相互关联。关系可以使用嵌入式和引用方法两种方式构建。这种关系可以是1:1,1:N,N:N。
假设我们需要存储用户和用户的地址。而一个用户可以有多个地址,这就是一个1:N的关系。
以下是用户文档结构:
{
"_id":10999110,
"name": "Maxsu",
"contact": "13888990021",
"dob": "1992-10-11"
}
以下是地址(address)文档的结构:
{
"_id":12200,
"building": "Hainan Building NO.2100",
"pincode": 571100,
"city": "Haikou",
"province": "Hainan"
}
下面分别通过嵌入式和引用式两种方式来构建关系:
嵌入式
在嵌入式方法中,我们直接将地址文档(address)嵌入到用户(user)文档中去。
{
"_id": 21000100,
"contact": "13800138000",
"dob": "1991-11-11",
"name": "Maxsu",
"address": [
{
"building": "Hainan Building NO.2100",
"pincode": 571100,
"city": "Haikou",
"province": "Hainan"
},
{
"building": "Sanya Building NO.2100",
"pincode": 572200,
"city": "Sanya",
"province": "Hainan"
},
]
}
这种方式将数据保存在同一个文档中,数据的检索和维护比较容易。
> db.users.findOne({"name":"Maxsu"},{"address":1, "name":1})
但是如果嵌入式文档不断增大,就会影响到读写的性能。
引用
这是设计规范化关系的方法。 在这种方法中,用户和地址文件将分别维护,但用户文档将包含一个将引用地址文档的id字段的字段。
我们可以将addresswen文档存在其它的集合中,将其引用添加到用户文档即可。我们使用使用DBRefs来引用文档。它适用于文档引用多个集合中的文档的情况。
{
"_id": 21000100,
"contact": "13800138000",
"dob": "1991-11-11",
"name": "Maxsu",
"address": [
{
"$ref":"address_home",
"$id":ObjectId("12200"),
"$db":"myDB"
},
{
"$ref":"address_home",
"$id":ObjectId("12201"),
"$db":"myDB"
},
]
}
DBRefs中有三个字段 -
-
$ref
- 此字段指定引用文档的集合 -
$id
- 此字段指定引用文档的_id
字段 -
$db
- 这是一个可选字段,并包含引用文档所在的数据库的名称
接下来我们就可以通过引用查到用户的地址。
>var user = db.users.findOne({"name":"Maxsu"})
>var dbRef = user.address
>db[dbRef.$ref].findOne({"_id":(dbRef.$id)})
12.Map Reduce
Map-reduce是将大量数据合并为有用的聚合结果的数据处理范例。MapReduce通常用于处理大型数据集。MongoDB使用MapReduce可以构建出大型复杂聚合查询。
以下是基本 mapReduce 命令的语法 -
>db.collection.mapReduce(
function() {emit(key,value);}, //map function
function(key,values) {return reduceFunction}, { //reduce function
out: collection,
query: document,
sort: document,
limit: number
}
)
map-reduce函数首先查询集合,然后将结果文档映射到发出的键值对,然后根据具有多个值的键进行减少。
在上面的语法 -
map是一个JavaScript函数,它将一个值与一个键映射并发出一个键值对;
reduce是一个javascript功能,可以减少或分组具有相同键的所有文档;
out指定map-reduce查询结果的位置;
query指定选择文档的可选选择条件;
sort指定可选的排序条件;
limit指定可选的最大文档数;
接下来我们来使用MapReduce
考虑存储用户帖子的以下文档结构。 该文档存储用户的user_name和帖子的状态(status)。
{
"post_text": "abcd",
"user_name": "maxsu",
"status":"active"
}
现在我们需要选出所有status为active的帖子,并根据user_name进行分组
>db.posts.mapReduce(
function() { emit(this.user_id,1); },
function(key, values) {return Array.sum(values)}, {
query:{status:"active"},
out:"post_total"
}
)
返回结果
{
"result" : "post_total",
"timeMillis" : 9,
"counts" : {
"input" : 4,
"emit" : 4,
"reduce" : 2,
"output" : 2
},
"ok" : 1,
}
结果表明:共有4个文档与查询匹配,mapper函数发出4哥键值对文档,最终将具有相同键的reduce函数分组的映射文档分解为2。在MapReduce函数后直接加find()函数就可以将我们想要的条目查询出来。查询结果如下:
{ "_id" : "tom", "value" : 2 }
{ "_id" : "maxsu", "value" : 2 }
13.文本搜索
从MongoDB 2.4开始,默认文本搜索功能是自动启用状态。
假设我们需要查询一些博客。我们先新建blogs集合。然后在blogs集合中插入一些文档。
db.blogs.insert({_id:1,title:"search",content:"MongoDB search"})
db.blogs.insert({_id:2,title:"Map Reduce",content:"Map-reduce"})
只有拥有Text index的collection才可以全文检索,因此我们需要构建Text index。一个collection只能有一个Text index,但是一个Text index可以有多个字段。
db.blogs.ensureIndex({title:"text",content:"text"})
接下来我们就可以通过索引去搜索。
简单的全文搜索
db.blogs.find({$text:{$search:"MongoDB"}})
返回值
{_id:1,title:"search",content:"MongoDB search"}
查询包含MongoDB不包含Map的记录
db.blogs.find({$text:{$search:"MongoDB -Map"}})
返回值
{_id:1,title:"search",content:"MongoDB search"}
查询含有MongoDB search的条目
db.blogs.find({$text:{$search:"\"MongoDB search\""}})
使用权重排序搜索结果
默认情况下,全文搜索是无顺序的,但是我们可以使用$meta textScore来获得每个文档对这个搜索的匹配程度,并使用其进行排序
1
db.blogs.find( {$text:{$search:"MongoDB"}}, {score:{$meta:"textScore"}} ).sort({score:{$meta:"textScore"}})