第一章 MongoDB简介
MongoDB是一个面向文档的数据库,而不是面向关系型数据库,具有更好的扩展性。
MongoDB采用的是横向扩展,面向文档的数据模型使其可以很容易地在多台服务器之间进行数据的分割。
功能:
- 创建数据
- 读取数据
- 删除数据
- 更新数据
- 索引
- 聚合
- 特殊的集合类型
- 文件存储
MongoDB并不具有一些在关系型数据库中常见的功能,如:连接(join)和复杂的多行事物(multirow transaction)
第二章 MongoDB基础知识
2.1 文档
文档是MongoDB的核心概念。是MongoDB中数据的基本单元,类似于关系型数据库中的行的概念。
文档就是键值对的一个有序集。
文档的键是字符串,注意:
- 键不能含有\0(空字符,用于表示键的结尾)。
- .和$具有特殊意义,不可随便使用。
- MongoDB不能有重复的键。
- 文档键值对都是有序的。
2.2 集合
集合就是一组文档。
集合是动态模式的。意思是一个集合里的文档可以各式各样。
一般都是将同类的文档放在一个集合里。
- 集合名不能以“system.”开头,此为系统集合保留的前缀。
- 集合名中也不能包含“$”。
- 子集合,使用“.”分隔不同的命名空间的子集合。
- MongoDB中使用子集合组织数据非常高效,推荐。
2.3 数据库
多个文档组成集合,而多个集合则组成数据库。一个MongoDB实例可以承载多个数据库,每个数据库可以有0个或多个集合。
2.4 启动MongoDB
mongod
2.5 MongoDB shell简介
MongoDB自带JavaScript shell,可以在shell中使用命令行与MongoDB实例交互。
运行shell:
mongo
2.6 数据类型
(1)基本数据类型
- null
- bool
- 数值
- 字符串
- 日期:{“x”:new Date() }
- 正则表达式
- 数组:{“x”: ["a", "b"]}
- 内嵌文档:文档可以嵌套在其他文档内;
- 对象id:MongoDB中存储的文档必须有一个"_id"的键,12字节的ID,是文档的唯一标识。
- 二进制数据
- 代码:查询和文档中可以包括任意JavaScript代码,{“x”:function() {/* */}}
2.7 MongoDB Shell的使用
第三章 创建、更新和删除文档
3.1 插入并保存文档
> db.foo.insert({"bar":"baz"})
批量插入:
> db.foo.batchInsert([{"_id":0},{"_id":1},{"_id":2}])
使用find查询文档可得:
> db.foo.find()
{"_id":0}
{"_id":1}
{"_id":2}
3.2 删除文档
> db.foo.remove()
删除foo集合中所有的文档,但是不会删除集合本身。
还可以使用drop(),速度更快,把整个集合都删除。
3.3 更新文档
> db.user.update({"name":"joe"}, joe)
3.4 写入安全机制
用于控制写入的安全级别。
第四章 查询
- 使用find或者findOne函数和查询文档对数据库执行查询;
- 使用$条件查询实现范围查询、数据集包含查询、不等式查询等;
- 查询将会返回一个数据集游标,在需要时可以将文档批量返回。
4.1 find简介
查询就是返回一个集合中文档的子集,子集的范围是从0个文档到整个集合。
> db.c.find()
将会匹配集合c中的所有的内容,返回集合c中所有的文档。
想查找"age"为27的所有文档,可以直接将这样的键值写进查询文档即可:
> db.user.find({"age": 27})
若是有多个查询条件时:
> db.user.find({"username":"Lee", "age": 24})
4.2 查询条件
- "$lt" : <
- "$gt" : >
- "$lte" : <=
- "$gte" : >=
> db.users.find({"age" : {"$gt" : 18, "$lte" : 30}})
OR查询
- "$in":可以用来查询一个键的多个值;
- "$or":可以在多个键中查询任意的值。
4.3 特定类型的查询
- null:null类型可以匹配到自身;
> db.c.find({"y" : null})
- 正则表达式
能够灵活的匹配字符串。
如:查找名为Joe或joe的用户:
> db.user.find({"name": /joe/i})
"i"为正则表达式标志
若还想匹配"joey"这样的键,可以用:
> db.user.find({"name": /joey?/i})
MongoDB使用Perl兼容的正则表达式(PCRE)库来匹配正则表达式。
- 查询数组
1. $all
如果通过多个元素老匹配数组,要用$all。如创建一个包含3个元素的集合,
> db.food.insert({"_id":1, "fruit":["apple", "banana","peach"]})
> db.food.insert({"_id":2, "fruit":["apple", "kumquat","orange"]})
> db.food.insert({"_id":3, "fruit":["cherry", "banana","apple"]})
查找既有"apple"又有"banana"的文档:
> db.food.find({fruit:{$all:["apple","banana"]}})
{"_id":1, "fruit":["apple", "banana","peach"}
{"_id":3, "fruit":["cherry", "banana","apple"]}
2. $size
可以用来查询特定长度的数组。
> db.food.find({"fruit":3}})
3. $slice操作符
find的第二个参数是可选的,可以指定需要返回的键。可以返回某个键匹配的数组元素的一个子集。
如返回一个博客文章的前十条评论:
> db.blog.posts.findOne(criteria,{"comments":{"$slice":10}})
也可以返回后十条评论:
> db.blog.posts.findOne(criteria,{"comments":{"$slice":-10}})
$slice也可以指定偏移值以及希望返回的元素的数量:
> db.blog.posts.findOne(criteria,{"comments":{"$slice":[10, 5]}})
会跳过前十个元素,返回11-15元素。
4. 数组和范围查询的相互作用
文档中的标量(非数组元素)必须与查询条件中的每一条语句相匹配。如有下文档:
{"x":5}
{"x":15}
{"x":25}
{"x":[5,25]}
db.test.find({"x":{"$gt":10, "$lt":20}})
{"x":15}
{"x":[5,25]}
数组[5,25]中5和25都不位于10-20之间,但是也返回了,因为其元素匹配任意一条条件都会返回。
> db.test.find({"x":{"$gt":10,"$lt":20}}).min({"x":10}).max({"x":20})
{"x":15}
现在只会遍历值在10-20之间的索引。
- 查询内嵌文档
两种方式查询内嵌文档:
(1) 查询整个文档;
> db.people.find({"name":{"first":"Joe", "last":"Schmo"}})
这种整个文档的查询还与顺序有关,必须精确匹配;
(2)只针对其键值对进行查询;
> db.people.find({"name.first:"Joe", "name.last":"Schmo"}})
![](https://i-blog.csdnimg.cn/blog_migrate/1be59d1b0e92b1940518a5ec7153f39a.png)
查找Joe发表的5分以上的评论:
> db.blog.find({"comments":{"$elemMatch":{"author":"Joe","score":{"$gte":5}}}})
"$elemMatch"将限定条件进行分组,仅当需要对一个内嵌文档的多个键操作时才会用到。
4.4 $where查询
"$where"最常见的应用是比较文档中的两个键的值是否相等。
4.5 游标
数据库使用游标返回find的执行结果。
4.6 数据库命令(database command)
如删除集合使用"drop"数据库命令执行:
> db.runCommand({"drop":"test"});
数据库命令总会返回一个包含"ok"键的文档。"ok"的值为1,说明命令执行成功;"ok"为0,执行失败。
第二部分 设计应用
第五章 索引
5.1 索引简介
索引可以根据给定的字段组织数据,让MongoDB能够非常快的找到目标文档。
如在username字段上创建一个索引:
> db.users.ensureIndex({"username":1})
(1)复合索引
在"age"和"username"上建立索引:
> db.users.ensureIndex({"age":1,"username":1})
三种查询方式:
> db.users.find({"age":21}).sort({"username":-1})
> db.users.find({"age":{"$gte":21,"$lte":30}})
> db.users.find({"age":{"$gte":21,"$lte":30}}).sort({"username":1})
5.2 使用explain()和hint()
explain()可以提供大量与查询相关的信息。
最常见的explain()输出有两种类型:使用索引的查询和不适用索引的查询。
对于使用符合索引的查询,最简单情况下的explain()输出:
> db.users.find({"age":42}).explain()
![](https://i-blog.csdnimg.cn/blog_migrate/7c1aa6e7c7f2c84db8bc341ef1b09bc4.png)
- "cursor" : "BtreeCursor age_1_username_1":
BtreeCursor 表示本次查询使用了索引,{"age":1, "username":1}
- "isMultiKey": false
说明本次查询是否使用多键索引。
- "n": 8332
本次查询返回的文档数量
- "nscannedObjects": 8332
按照索引指针去磁盘上查找实际文档的次数。
- "nscanned": 8332
若没有索引,这为查找过的索引条目数量
- "scanAndOrder": false
是否在内存中对结果进行了排序
- "indexOnly" : false
是否只使用索引就能完成此次查询。
- "nYields": 0
为了让写去请求能够顺利执行,本次查询暂停的次数
- "millis": 91
数据库执行本次查询所耗费的毫秒数。时间越短,说明查询效率越高。
- "indexBounds" : {...}
描述了索引的使用情况,给出了索引的遍历范围。
如果发现MongoDB使用的索引与自己希望它使用的索引不一样,可以使用hint()强制MongoDB使用特定的索引。
5.5 索引管理
使用ensureIndex函数创建新的索引,对于一个集合,每个索引只需创建一次。
第六章 特殊的索引和集合
介绍MongoDB中一些特殊的集合和索引类型:
- 用于类队列数据的固定集合;
- 用于缓存的TTL索引;
- 用于简单字符串搜索的全文索引;
- 用于二维平面和球体空间的地理空间索引;
- 用于存储大文件的GridFS。
6.1 固定集合
固定集合需要事先创建好,而且它的大小是固定的。
如果固定集合被占满时,再插入新的文档,固定集合会自动将最老的文档从集合中删除。
访问模式:数据被顺序写入磁盘上的固定空间。
固定集合可以用于记录日志,尽管不够灵活。虽然可以在创建时指定集合的大小,但是无法控制什么时候数据会被覆盖。
(1)创建固定集合
使用createCollection函数:
> db.createCollection("my_collection", {"capped":true, "size":100000}) ;
还可以指定固定集合中文档的和数量:
> db.createCollection("my_collection2", {"capped":true, "size":100000, "max":100}) ;
(2)自然排序(natural sort)
固定集合中文档是按照文档被插入的顺序保存的,自然排序就是文档的插入顺序。
> db.my_collection.find().sort({"$natural":-1})
(3)循环游标(tailable cursor)
当循环游标的结果集被取光之后,游标不会被关闭。当有新的文档插入到集合中时,循环游标会继续取到结果。由于普通的集合并不维护文档的插入顺序,所以循环游标智能在固定结合中。
(4)没有_id索引的集合
在调用createCollection创建集合时指定autoIndexId选项为false,创建集合时就不会自动在"_id"上创建索引。实践中不建议这么做。
如果创建一个没有"_id"索引的集合,则永远都不能复制它所在的mongod了。
6.2 TTL索引(time-to-live index)
允许为每一个文档设置一个超时时间。一个文档到达预设置的老化程度之后就会被删除。
在ensureIndex中指定expireAfterSecs选项就可以设置一个TTL索引。
> // 超时时间为24h
> db.foo.ensureIndex({"lastUpdates":1}, {"expireAfterSecs":60*60*24})
这样就在lastUpdated字段上建立一个TTL索引。
6.3 全文本索引
使用全文本索引可以非常快地进行文本搜索,就如同内置了多种语言分词机制的支持一样。
6.4 地理空间索引
MongoDB支持几种类型的地理空间索引。最常用的是2dsphere索引(用于地球表面类型的地图)和2d索引(用于平面地图和时间连续的数据)
6.5 使用GridFS存储文件
GridFS是MOngoDB的一存储机制,可以用来存储大型二进制文件。
- 使用GridFS能够简化栈;
- GridFS会自动平衡已有的复制或者为MongoDB设置的自动分片,更容易对文件存储做故障转移或者横向扩展会容易;
- GridFS可以比较容易的解决其他一些文件系统可能会会遇到的问题
- GridFS中文件存储集中度会比较高。
缺点:
- 性能比较低;
- 若修改GridFS上的文档,只能先将已有的文档删除,然后再将整个文档重新保存。
第七章 聚合
- 聚合框架
- MapReduce
- 几个简单聚合命令:count、distinct和group
7.1 聚合框架
使用聚合框架可以对集合中的文档进行变换和组合。可以用多个构建创建一个管道(pipeline)。构建有:筛选(filtering)、投射(projecting)、分组(grouping)、排序(sorting)、限制(limiting)和跳过(skipping)。
(1){"$project": {"author" : 1}}
可以将author从每个文档里投射出来。
(2){"$group" : {"_id" : "$author", "count" : {"$sum" : 1}}}
将作者按照名字排序,某个作者的名字每出现一次,就会对这个作者的“count”加1。
(3){"$sort" : {"count" : -1}}
会对结果集中的文档根据"count"字段按照降序排列。
(4){"$limit" : 5}
将最终的返回结果限制为当前结果中的前5个文档。
7.2 管道操作符
(1)$match
$match用于对文档集合进行筛选,之后就可以在筛选得到的文档自己上做聚合。
(2)$project
使用$project可以从子文档中提取字段,可以重命名字段,还可以在这些字段上进行一些有意义的操作。
如只返回包含一个“author”字段:
> db.articles.aggregate({"$project" : {"author":1, "_id": 0}})
(3)$group
$group操作可以将文档依据特定字段的不同值进行分组。
如果选定了需要进行分组的字段,就可以将选定的字段传递给"$group"函数的"_id"字段。如:
{"$group":{"_id":"$day"}}
{"$group":{"_id":"$grade"}}
{"$group":{"_id":{"state":"$state", "city":"$city"}}}
算数操作符:
- "$sum" : value
对于分组中的,每一个文档,将value与计算的结果进行相加。
> db.sales.aggregate(
{
"$group" : {
"_id" : "$country",
"totalRevenue" : {"$sum": "$revenue"}
}
})
- "$avg" : value
返回每个分组的平均值。
> db.sales.aggregate(
{
"$group" : {
"_id" : "$country",
"totalRevenue" : {"$avg": "$revenue"},
"numSales" : {"$sum" : 1}
}
})
极值操作符:
- "$max" : expr
返回分组内的最大值。
- "$min" : expr
返回分组内的最小值。
- "$first" : expr
返回分组的第一个值,忽略后边的所有值。
- "$last" : expr
与相反"$first" ,返回分组的最后一个值。
(4)$unwind
拆分(unwind)可以将数组中每一个值拆分成单独的文档。
> db.blog.aggregate({"$unwind":"$comments"})
将每一条评论拆分成单独的文档。
(5)$sort
可以根据任意字段进行排序。
(6)$limit
$limit会接收一个数字n,返回结果集中的前个文档。
(7)$skip
$skip也会接收一个数字n,丢弃结果集中的前n个文档。
7.3 MapReduce
MapReduce主要用于聚合。MapReduce使用JavaScript作为“查询语言”,因此能够表达任何复杂的逻辑。然后这种强大的代价是:MapReduce非常的慢,不应该用在实时的数据分析中。
MapReduce可以在多台服务器之间并行执行。将一个大问题拆分成多个小问题,将各个小问题发送到不同的服务器上,每台机器负责完成一部分工作。
7.4 聚合命令
(1)count
最简单的聚合命令,用于返回集合中的文档的数量;
> db.foo.count()
0
> db.foo.insert({"x"})
> db.foo.count()
1
不论集合有多大,count都会很快返回总的文档数量。
(2)distinct
distinct用来找出给定键的所有不同的值。使用时必须指定集合和键。
> db.runCommand({"distinct":""people},"key":"age")
(3)group
使用group可以执行更复杂的聚合。
group与SQL中的groupby相似。
第八章 应用程序设计
- 内嵌数据和引用数据之间的权衡;
- 优化技巧;
- 数据一致性;
- 模式迁移;
- 不适合使用MongoDB作为数据存储的场景。
8.1 范式化和反范式化
范式化(normalization):将数据分散到多个不同的集合中,不同集合之间可以相互引用数据。
反范式化(denormalization):每个文档所需的数据都嵌入在文档内部。