注:本地学习基于MongoDB v4.2版本。
MongoDB是一个文档数据库,它是为方便开发和扩展而设计的。
MongoDB提供了社区版和企业版本,相较于社区版,MongoDB企业版增加了面向企业的特性,如LDAP和Kerberos支持、磁盘加密和审计。
MongoDB属于文档数据库。MongoDB中的记录是一个文档,是一个由字段和值对组成的数据结构。MongoDB文档类似于JSON对象。字段的值可以包括其他文档、数组和文档数组。
主要特点:
我们先谈一下使用,然后再介绍安装。
1.连接数据库:
直接输入“db”,返回默认的数据库“test”。 如果我们想使用其它或创建数据库,直接使用“use exec”,则会创建exec数据库,并自动切换到该数据库。
2.数据库和集合
MongoDB在集合中存储BSON文档,即数据记录。一条记录相当于关系数据库的一条数据,一个集合相当于数据表。
2.1创建数据库
如果数据库不存在,MongoDB将在您第一次为该数据库存储数据时创建数据库。因此,您可以切换到一个不存在的数据库,并在mongo shell中执行以下操作:
use myNewDB
db.myNewCollection1.insertOne( { x: 1 } )
这个时候,使用show dbs命令,即可看到我们创建的myNewDB数据库。
insertOne()操作创建数据库myNewDB和集合myNewCollection1(如果它们还不存在)。确保数据库和集合名称都遵循MongoDB命名限制。
2.2 集合
MongoDB将文档存储在集合中。集合类似于关系数据库中的表。如果一个集合不存在,MongoDB会在您第一次存储该集合的数据时创建该集合。
MongoDB提供了db.createCollection()方法来显式创建具有各种选项的集合,例如设置最大大小或文档验证规则。如果没有指定这些选项,就不需要显式地创建集合,因为当您第一次为集合存储数据时,MongoDB会创建新的集合。
2.3 文档验证
默认情况下,集合不要求其文档具有相同的模式;也就是说,单个集合中的文档不需要具有相同的字段集,而字段的数据类型在集合中的不同文档之间可能有所不同。
MongoDB 3.4引入
本章介绍以下几点:
3.1 创建视图
为了创建或定义一个视图,MongoDB 3.4引入:
create命令上的viewOn和pipeline选项(db.createCollection
助手):
db.runCommand( { create: , viewOn: , pipeline: } )
或者指定视图的默认排序规则:
db.runCommand( { create: , viewOn: , pipeline: , collation: } )
或通过db.createView():
db.createView(, , , )
字段 解释
create 视图名称
viewOn 源collection或源视图,指定依赖哪个集合或视图而建的
pipeline 聚合管道,作为聚合管道片段的一部分,参与聚合操作
collation 为view指定一个针对不同语言的排序规则
使用db.createView创建视图时,需要制定一个排序规则,不指定的话,创建报错。
3.1.1 查看视图
3.2 行为
视图表现出以下行为:
3.2.1 只读
视图是只读;对视图的写操作将出错。
下列读操作可以支持视图:
db.collection.find()
db.collection.findOne()
db.collection.aggregate()
db.collection.countDocuments()
db.collection.estimatedDocumentCount()
db.collection.count()
db.collection.distinct()
3.2.2 索引使用和排序操作
例如,以下操作无效:
db.view.find().sort({$natural: 1})
3.2.3 投影的限制
视图上的find()操作不支持以下投影操作:
$
$elemMatch
$slice
$meta
3.2.4 名字不可变
视图的名字不可以重命名。
3.2.5 视图创建
视图在读取操作期间按需计算,MongoDB作为底层聚合管道的一部分对视图执行读取操作。因此,视图不支持以下操作:
如果用于创建视图的聚合管道取消了_id字段,则视图中的文档没有_id字段。
3.2.6 分片视图
如果视图的底层集合是分片的,则认为它们是分片的。因此,您不能在$lookup和$graphLookup操作中为from字段指定切分视图。
3.2.7 视图和排序
3.2.8 公共视图定义
列出集合的操作,例如db.getCollectionInfos()和db.getCollectionNames(),在它们的输出中包括视图。
db.getCollectionInfos():显示该数据库所有的集合和视图信息
db.getCollectionNames():显示该数据库所有的集合和视图名称。
重要:
视图定义是公共的;例如db.getCollectionInfos()和对视图的解释操作将包括定义视图的管道。因此,要避免直接引用视图定义中的敏感字段和值。
3.3 删除视图
要删除视图,请使用视图上的db.collection.drop()方法。
例如删除视图testView:
3.4 修改视图
您可以通过删除和重新创建视图或使用collMod命令来修改视图。
3.5 支持的操作
除本页所述的限制外,下列操作提供了对视图的支持:
Commands | Methods |
---|---|
create |
|
collMod |
|
|
|
|
|
从4.2版开始,MongoDB为聚合管道添加了$merge阶段。此阶段可以将管道结果合并到现有集合中,而不是完全替换该集合。此功能允许用户创建随需应变的物化视图,其中在每次运行管道时都可以更新输出集合的内容。
举例:
假设在2019年1月底,collection bakesales按项目包含销售信息:
db.bakesales.insertMany( [
{ date: new ISODate("2018-12-01"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2018-12-02"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("90") },
{ date: new ISODate("2018-12-02"), item: "Cake - Red Velvet", quantity: 10, amount: new NumberDecimal("200") },
{ date: new ISODate("2018-12-04"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2018-12-04"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2018-12-05"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-25"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-26"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-26"), item: "Cake - Carrot", quantity: 2, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-26"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-27"), item: "Pie - Chocolate Cream", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-27"), item: "Cake - Peanut Butter", quantity: 5, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-27"), item: "Tarts - Apple", quantity: 3, amount: new NumberDecimal("12") },
{ date: new ISODate("2019-01-27"), item: "Cookies - Chocolate Chip", quantity: 12, amount: new NumberDecimal("48") },
{ date: new ISODate("2019-01-27"), item: "Cake - Carrot", quantity: 5, amount: new NumberDecimal("36") },
{ date: new ISODate("2019-01-27"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-01-28"), item: "Cookies - Chocolate Chip", quantity: 20, amount: new NumberDecimal("80") },
{ date: new ISODate("2019-01-28"), item: "Pie - Key Lime", quantity: 3, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-01-28"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
] );
1. 定义随需应变的物化视图
下面的updateMonthlySales函数定义了一个monthlybakesales物化视图,其中包含累积的月度销售信息。在本例中,该函数接受date参数,仅更新从特定日期开始的每月销售信息。
updateMonthlySales = function(startDate) {
db.bakesales.aggregate( [
{ $match: { date: { $gte: startDate } } },
{ $group: { _id: { $dateToString: { format: "%Y-%m", date: "$date" } }, sales_quantity: { $sum: "$quantity"}, sales_amount: { $sum: "$amount" } } },
{ $merge: { into: "monthlybakesales", whenMatched: "replace" } }
] );
};
$match阶段过滤数据,只处理那些大于或等于startDate的销售。
$group阶段按年月对销售信息进行分组。本阶段输出的文件形式为:
{ "_id" : "", "sales_quantity" : , "sales_amount" : }
$merge阶段将输出写入monthlybakesales集合。
根据_id字段(未分片输出集合的默认字段),阶段检查聚合结果中的文档是否与集合中的现有文档匹配:
2. 执行首次运行
对于初始运行,您可以传入一个新的ISODate日期(“1970-01-01”):
updateMonthlySales(new ISODate("1970-01-01"));
初次运行后,monthlybakesales包含以下文档,即:
db.monthlybakesales.find().sort( { _id: 1 } )返回以下:
{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 86, "sales_amount" : NumberDecimal("896") }
3.刷新物化视图
假设在2019年2月的第一周,bakesales集合更新了更新的销售信息;具体来说,是1月和2月的额外销售。
db.bakesales.insertMany( [
{ date: new ISODate("2019-01-28"), item: "Cake - Chocolate", quantity: 3, amount: new NumberDecimal("90") },
{ date: new ISODate("2019-01-28"), item: "Cake - Peanut Butter", quantity: 2, amount: new NumberDecimal("32") },
{ date: new ISODate("2019-01-30"), item: "Cake - Red Velvet", quantity: 1, amount: new NumberDecimal("20") },
{ date: new ISODate("2019-01-30"), item: "Cookies - Chocolate Chip", quantity: 6, amount: new NumberDecimal("24") },
{ date: new ISODate("2019-01-31"), item: "Pie - Key Lime", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-01-31"), item: "Pie - Banana Cream", quantity: 2, amount: new NumberDecimal("40") },
{ date: new ISODate("2019-02-01"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") },
{ date: new ISODate("2019-02-01"), item: "Tarts - Apple", quantity: 2, amount: new NumberDecimal("8") },
{ date: new ISODate("2019-02-02"), item: "Cake - Chocolate", quantity: 2, amount: new NumberDecimal("60") },
{ date: new ISODate("2019-02-02"), item: "Cake - Peanut Butter", quantity: 1, amount: new NumberDecimal("16") },
{ date: new ISODate("2019-02-03"), item: "Cake - Red Velvet", quantity: 5, amount: new NumberDecimal("100") }
] )
要刷新一月和二月的monthlybakesales数据,再次运行该函数以重新运行聚合管道,从new ISODate(“2019-01-01”)开始。
updateMonthlySales(new ISODate("2019-01-01"));
已更新了monthlybakesales的内容,以反映bakesales集合中的最新数据;即:
db.monthlybakesales.find().sort( { _id: 1 } )
返回结果:
{ "_id" : "2018-12", "sales_quantity" : 41, "sales_amount" : NumberDecimal("506") }
{ "_id" : "2019-01", "sales_quantity" : 102, "sales_amount" : NumberDecimal("1142") }
{ "_id" : "2019-02", "sales_quantity" : 15, "sales_amount" : NumberDecimal("284") }
4. 额外的信息
$merge
阶段:
本节介绍:
1. 概述
上限集合是固定大小的集合,支持基于插入顺序插入和检索文档的高吞吐量操作。Capped集合的工作方式类似于循环缓冲区:一旦集合填满了分配的空间,它就通过覆盖集合中最老的文档为新文档腾出空间。
提示:
可以考虑使用MongoDB的TTL (Time to Live)索引来替代有上限的集合。正如通过设置TTL从集合中获取过期数据中所描述的,这些索引允许您根据日期类型字段的值和索引的TTL值来过期和从常规集合中删除数据。
TTL索引与上限集合不兼容。
2. 行为
2.1 插入顺序
有限集合保证可插入顺序。因此,查询不需要索引来按插入顺序返回文档。没有这种索引开销,有上限的集合可以支持更高的插入吞吐量。
2.2 自动删除最古老的文档
为了给新文档腾出空间,capped集合自动删除集合中最老的文档,而不需要脚本或显式的删除操作。
考虑以下有盖集合的潜在用例:
例如,oplog.rs在复制集中存储操作日志使用一个capped集合。从MongoDB 4.0开始,与其他有上限的集合不同,oplog可以超过其配置的大小限制,以避免删除大多数提交点。
2.3 _id索引
默认情况下,上限集合有一个_id字段和_id字段上的索引。
3. 限制和建议
3.1 更新
如果您计划在一个有上限的集合中更新文档,请创建一个索引,以便这些更新操作不需要对集合进行扫描。
3.2 文档个数
在3.2版本中进行了更改。
如果更新或替换操作更改了文档大小,则操作将失败。
3.3 文档删除
不能从一个有上限的集合中删除文档。要从集合中删除所有文档,请使用drop()方法删除集合并重新创建有上限的集合。
3.4 分片
不能分割一个有上限的集合。
3.5 查询效率
使用自然排序从集合中有效地检索最近插入的元素。这与日志文件中的tail有点类似。
3.6 聚合$out
聚合管道阶段$out无法将结果写入有上限的集合。
3.7 事务
从MongoDB 4.2开始,你不能在事务中写集合封顶。在事务中仍然支持从上限集合读取数据。
4. 程序
4.1 创建一个有上限的集合
您必须使用db.createCollection()方法显式地创建有上限的集合,该方法是create命令的mongo shell中的一个助手。当创建一个有上限的集合时,您必须以字节为单位指定集合的最大大小,MongoDB将为集合预先分配该大小。上限集合的大小包括用于内部开销的少量空间。
db.createCollection( "log", { capped: true, size: 100000 } )
如果size字段小于或等于4096,则集合的上限为4096字节。否则,MongoDB将把提供的大小提升为256的整数倍。
此外,你也可以在以下文件中使用max字段指定集合的最大文档数量:
db.createCollection("log", { capped : true, size : 5242880, max : 5000 } )
重要的:
大小参数始终是必需的,即使在指定最大文档数量时也是如此。如果集合在达到最大文档数之前达到最大大小限制,MongoDB将删除旧文档。
4.2 查询一个有上限的集合
如果在没有指定顺序的上限的集合上执行find(), MongoDB保证结果的顺序与插入顺序相同。
要按反向插入顺序检索文档,可以发出find()和sort()方法,其中$natural参数设置为-1,如下面的示例所示:
db.cappedCollection.find().sort( { $natural: -1 } )
4.3 检查集合是否有上限
使用isCapped()方法判断一个集合是否有上限,如下所示:
db.collection.isCapped()
4.4 将集合转换为Capped
您可以使用convertToCapped命令将一个无上限的集合转换为一个有上限的集合:
db.runCommand({"convertToCapped": "mycoll", size: 100000});
size参数指定带帽集合的大小(以字节为单位)。
它在操作期间持有一个数据库独占锁。锁定同一数据库的其他操作将被阻塞,直到操作完成。查看一些常见的客户端操作占用哪些锁?用于锁定数据库的操作。
4.5 Tailable光标
您可以使用带有上限集合的tailable游标。类似于Unix tail -f命令,tailable游标“tail”结束一个有上限的集合。当新文档被插入到capped集合中时,可以使用tailable游标继续检索文档。
有关创建可调整游标的信息,请参阅可调整游标。
上限集合(固定集合)应用场景:
比如日志文件,聊天记录,通话信息记录等只需保留最近某段时间内的应用场景,都会使用到MongoDB的固定集合。