MongoDB:入门学习笔记
入门介绍
基本介绍
官方文档: https://www.mongodb.com/zh-cn/docs/
MongoDB是一个基于分布式文件存储的数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。以下是对MongoDB的详细说明:
- 产品类型:基于分布式文件存储的数据库。
- 编写语言:C++。
- 主要特点:高性能、易部署、易使用,存储数据非常方便。
- 数据存储格式:BSON(Binary Serialized Document Format),一种类似JSON的二进制序列化文档格式,支持嵌套对象和数组,使用高效的二进制数据存储,包括大型对象(如视频)。
核心特性
- 面向集合存储:数据被分组存储在数据集中,被称为一个集合(Collection)。每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定义任何模式(schema)。
- 模式自由:对于存储在MongoDB数据库中的文件,不需要知道它的任何结构定义。如果需要的话,完全可以把不同结构的文件存储在同一个数据库里。
- 支持动态查询:MongoDB支持丰富的查询操作,几乎支持SQL中的大部分查询。同时,它提供的查询语言非常强大,其语法有点类似于面向对象的查询语言。
- 支持完全索引:可以在任意属性上建立索引,包含内部对象。MongoDB的索引和RDBMS的索引基本一样,可以在指定属性、内部对象上创建索引以提高查询的速度。除此之外,MongoDB还提供创建基于地理空间的索引的能力。
- 高可用性和可靠性:MongoDB具有内置的复制和故障转移功能,可以保证数据的高可用性和可靠性。它支持主从复制机制,可以实现数据备份、故障恢复、读扩展等功能。而基于副本集的复制机制提供了自动故障恢复的功能,确保了集群数据不会丢失。
- 可扩展性:MongoDB支持水平扩展,可以方便地增加服务器和存储空间,以满足不断增长的数据需求。其自动分片功能支持水平的数据库集群,可动态添加机器,实现海量数据的分布式存储。
- 强大的聚合工具:MongoDB除了提供丰富的查询功能外,还提供强大的聚合工具,如count、group等,支持使用MapReduce完成复杂的聚合任务。
应用场景
MongoDB因其高性能、可扩展性和灵活性,被广泛应用于各种场景,包括但不限于:
- Web应用数据存储:MongoDB可以用于为Web应用提供可扩展的高可用、高性能数据存储解决方案。
- 大数据存储和处理:MongoDB适用于存储和处理大量的非结构化数据,如日志数据、传感器数据、社交媒体数据等。
- 实时分析和报表:MongoDB的数据模型和查询语言可以方便地进行实时分析和报表生成,支持复杂的聚合操作和数据分析。
- 内容管理系统(CMS):MongoDB可以用作内容管理系统的后端数据库,存储和管理大量的文章、图片、视频等内容。
- 社交网络应用:MongoDB的灵活的数据模型和高性能的读写能力使其成为社交网络应用的理想选择。
- 物联网(IoT)应用:MongoDB可以存储和处理大规模的物联网设备生成的数据。
MongoDB的结构
MongoDB的结构可以从多个层面进行解析,主要包括其数据存储结构、技术架构以及集群架构等方面。
数据存储结构
MongoDB是一个面向文档的NoSQL数据库,它以BSON(Binary JSON)格式存储数据。BSON是JSON文档的二进制表示,虽然与JSON类似,但BSON支持更多的数据类型,如日期和二进制数据。MongoDB的数据存储结构基于文档模型,主要由以下几个单元组成:
- 文档(Document):MongoDB中的基本数据单位,由键值对组成,类似于JSON对象。文档可以包含各种数据类型,包括字符串、整数、浮点数、布尔值、日期、数组、嵌套文档等。每个文档都有一个唯一的标识符(_id),它可以是任何数据类型,但通常是一个唯一的字符串。
- 集合(Collection):一个集合可以包含多个文档,相当于关系型数据库中的表格(Table)。集合中的文档可以有不同的字段,没有固定的表结构,这使得MongoDB在应对需求变化和数据模式不确定的情况下更加灵活。
- 数据库(Database):等同于关系型数据库中的数据库概念,一个数据库中可以包含多个集合。您可以在MongoDB中创建多个数据库。
技术架构
MongoDB的技术架构可以分为以下几个层次:
- 数据存储层:MongoDB使用内存映射文件存储引擎(如WiredTiger)将数据持久化到磁盘。存储引擎负责数据的读写、压缩、加密等操作。
- 数据模型层:MongoDB的数据模型基于文档,支持嵌套文档和数组。这使得MongoDB能够存储复杂的数据结构,如树形结构、图形数据等。
- 查询语言层:MongoDB使用基于文档的查询语言(MongoDB Query Language, MQL),支持丰富的查询操作符和聚合管道。MQL允许用户根据文档的结构和内容进行查询,实现灵活的数据检索和分析。
- 索引层:MongoDB支持多种类型的索引,如单字段索引、复合索引、地理空间索引等。索引可以提高查询性能,加快数据的检索速度。
- 复制和分片层:MongoDB支持主从复制和分片集群,确保数据的高可用性和可扩展性。主从复制可以实现数据的备份和故障恢复;分片集群可以将数据分布在多个节点上,实现水平扩展和负载均衡。
- 事务层:从MongoDB 4.0版本开始,MongoDB支持多文档事务,确保数据的一致性和完整性。
- 安全性和认证层:MongoDB提供了一系列安全特性,如身份验证、授权、加密等,以保护数据在传输和存储过程中的安全。
集群架构
MongoDB支持多种集群架构模式,以适应不同的应用场景和需求:
- 主从复制(Master-Slave):这是一种简单的复制模式,其中一台服务器被配置为主服务器(Master),负责处理所有的写操作和部分读操作,而其他服务器则作为从服务器(Slave),主要处理读操作以及作为主服务器的备份。然而,主从复制模式在现代MongoDB中已经不再推荐使用,因为它缺乏自动故障转移、数据一致性保障和灵活的读负载均衡能力。
- 副本集(Replica Set):副本集是现代MongoDB中用于实现数据冗余、高可用性和读负载均衡的主要方式。它包含一个主节点(Primary)和一个或多个从节点(Secondary)。副本集提供了自动故障转移、数据一致性保证以及对从节点读取能力的精细控制。副本集是目前MongoDB中主流且推荐的集群架构模式之一。
- 分片集群(Sharding):分片是MongoDB用于水平扩展、处理大规模数据集和高并发读写的架构模式。通过将数据划分为多个分片(shards),每个分片可以是一个副本集,数据根据分片键(shard key)分布在不同的分片上。此外,还包括配置服务器集群(Config Server)和路由进程(Mongos)。分片是现代MongoDB中另一种重要的集群架构模式,用于处理超出单个服务器或副本集处理能力的大规模数据集。
常用指令
基本指令
show dbs
或show databases
:显示当前的所有数据库。
show collections
或show tables
:显示数据库中所有的集合。
use 数据库名
:进入到指定的数据库中。
db
:这是一个变量,表示当前所处的数据库。
db.stats()
:返回当前数据库的统计信息。这个方法提供了一个关于数据库状态的概览,包括存储大小、索引大小、对象计数等。
CRUD操作
新增
向集合中插入一个文档:db.<collection>.insertOne(doc)
或db.<collection>.insertMany(doc)
。
【注】如果数据库或集合不存在,MongoDB会在你首次插入数据时自动创建数据库和集合。
insert()
是一个较旧的方法,可以接受单个文档对象,也可以接受一个包含多个文档的数组。
例1:插入单个文档
向test数据库中的stus集合中插入一个新的学生对象。
|
|
当我们向集合中插入文档时,如果没有给文档指定
_id
属性,则数据库会自动为文档添加_id
,该属性用来作为文档的唯一标识。
_id
我们可以自己指定,如果我们指定了数据库就不会在添加了,如果自己指定_id
,也必须确保它的唯一性。
MongoDB的id生成规则主要依赖于ObjectId类型,这是一个12字节(或24位十六进制字符串)的标识符,用于唯一标识MongoDB中的文档。ObjectId的生成规则可以归纳如下:
-
时间戳(4字节):ObjectId的前4个字节是一个Unix时间戳,表示ObjectId的生成时间,精确到秒。这个时间戳提供了秒级的时间唯一性,确保了ObjectId的时间有序性,并且能够在一定程度上反映文档的插入顺序。
需要注意的是,由于ObjectId中包含了生成时间的信息,当分布式系统中的机器的系统时间不同步时,可能会导致生成的ObjectId的时间顺序不准确。
-
机器标识(3字节):接下来的3个字节代表生成此ObjectId的机器的唯一标识符。这通常是基于机器的MAC地址或其他能够唯一标识机器的信息的散列值。这样可以保证不同机器生成的ObjectId不会冲突。
-
进程标识(2字节):随后的2个字节表示生成ObjectId的进程的标识符(PID)。这确保了即使在同一台机器上运行的多个MongoDB实例或进程,也能生成唯一的ObjectId。
-
自增计数器(3字节):最后的3个字节是一个计数器,它在具有相同时间戳的特定秒内递增。这个计数器确保了在同一秒内,在同一台机器上的同一个进程中创建的多个ObjectId也是唯一的。
例2:插入多个文档
|
|
多个文档需要用方括号[]
括起来,表示这是一个文档数组。每个文档都是一个对象,对象之间用逗号,
分隔。
查询
find()
方法允许你根据指定的条件来检索集合中的文档。如果没有指定任何条件,find()
方法将返回集合中的所有文档。
findOne()
方法用于查询符合条件集合中的第一个文档。
例1:查询集合中的所有文档
返回stus
集合中的所有文档
|
|
db.stus.find().pretty()
:可以使用pretty()
方法来美化输出结果。
查询文档中元素的个数
|
|
例2:根据特定字段查询
如:查询名字为"张三"的文档
|
|
查询班级为"计算机科学与技术1班"的文档
|
|
例3:不等于查询
查询分数不等于99的学生
|
|
例4:范围查询
查询分数在80到90(包括80和90)之间的学生
|
|
例5:组合条件查询
你可以通过$and
操作符(虽然在实际查询中,如果不使用$and
显式地写出来,MongoDB也会默认以$and
的方式处理多个条件),或者简单地列出所有条件(MongoDB隐式地使用$and
)来组合多个条件。
查询名字为"张三"且年龄为20的文档
|
|
或者使用
$and
显式地表示(通常不推荐,因为上面的写法已经足够且更简洁):
1 2 3 4 5 6
db.stus.find({ $and: [ { name: "张三" }, { age: 20 } ] })
例6:或条件查询
使用$or
进行或条件查询
查询名字为"张三"或班级为"计算机科学与技术2班"的文档
|
|
例7:查询并排序
可以使用sort()
方法对查询结果进行排序。
查询所有文档并按年龄升序排序
|
|
查询所有文档并按年龄降序排序
|
|
按照班级升序,再按照分数降序
|
|
例8:限制查询结果数量
使用limit()
方法可以限制查询结果的数量。
查询并只返回第一个文档
|
|
例9:跳过一定数量的文档
查询所有学生,但跳过前5个结果,返回之后的文档:
|
|
通常,skip()
方法与limit()
方法结合使用,以实现分页功能。
|
|
例10:投影查询(只返回部分字段)
查询所有学生的名字和分数,不返回其他字段
|
|
要包含或排除字段,可以在投影对象中将字段名设置为
1
(包含)或0
(排除)。
注意:在MongoDB中,默认会返回_id
字段,如果您不想返回它,需要显式设置_id: 0
。
例11:数组包含查询(使用$in
)
$in
操作符允许您查询字段值在指定数组中的文档。
例如,查询班级为"A班"或"B班"的学生
|
|
与$in
相反,$nin
操作符允许您查询字段值不在指定数组中的文档。
例如,查询班级既不是"A班"也不是"B班"的学生:
|
|
例12:字段存在性查询
查询存在email
字段的文档
|
|
相反,要查询不存在该字段的文档,可以将$exists
的值设置为false
。
修改
在MongoDB中,修改数据通常使用updateOne
、updateMany
、replaceOne
等命令。以下是根据您提供的stus
集合数据的一些修改数据的示例:
示例1:更新单个文档(updateOne
)
假设您想将名为"Alice"的学生的分数更新为85分:
|
|
这里,{ name: "Alice" }
是查询条件,{ $set: { score: 85 } }
是更新操作,表示将匹配到的文档的score
字段设置为85。
示例2:更新多个文档(updateMany
)
如果您想将所有"computer science 1"班级的学生分数增加5分:
|
|
这里,{ class: "computer science 1" }
是查询条件,{ $inc: { score: 5 } }
是更新操作,表示将匹配到的文档的score
字段增加5。
示例3:替换整个文档(replaceOne
)
如果您想替换名为"Denny"的学生的整个文档:
|
|
这里,{ name: "Denny" }
是查询条件,第二个参数是新的文档,它将替换掉匹配到的旧文档。
示例4:使用upsert
选项
如果您想在更新时不存在匹配的文档则插入一个新文档(即“更新或插入”),可以使用updateOne
或replaceOne
的upsert
选项:
|
|
在这个例子中,如果集合中没有名为"Charlie"且年龄为22的学生,MongoDB将插入一个新的文档。
示例5:从文档中移除指定的字段
移除名为"Eric"的学生的score
字段
|
|
在
$unset
的字段值中,我们实际上不需要指定要移除字段的新值(像在其他操作中那样),而是简单地将其设置为1
(或任何其他非零值,因为MongoDB在处理$unset
时不会检查这个值的具体内容)。但是,为了代码的可读性和一致性,通常建议将其设置为1
。
使用updateMany
移除多个文档的字段
如果我们想要从所有"computer science 1"班级的学生文档中移除score
字段,我们可以使用updateMany
:
|
|
这将匹配所有class
字段值为"computer science 1"的文档,并从这些文档中移除score
字段。
使用
$unset
时要小心,因为一旦字段被移除,就无法通过简单的查询来恢复它(除非你有备份或可以从其他来源重新生成该字段的数据)。
注意
- 在使用
updateOne
、updateMany
或replaceOne
时,请确保查询条件能够准确匹配到目标文档,以避免不必要的更新。 - MongoDB的更新操作是原子的,即一旦开始,就不会被其他操作中断。
删除
在MongoDB中,删除文档通常使用deleteOne
或deleteMany
方法。deleteOne
用于删除匹配查询条件的第一个文档,而deleteMany
用于删除所有匹配查询条件的文档。
remove
方法是早期版本中用于删除文档的一个方法,它的行为与deleteMany
非常相似,因为它默认会删除所有匹配查询条件的文档。也通过传递一个额外的参数,来控制remove
的行为,使其表现得像deleteOne
。
示例1:删除单个文档
要删除名为"Eric"的学生的文档:
|
|
这条命令会查找stus
集合中name
字段值为"Eric"的第一个文档,并将其删除。
示例2:删除多个文档
要删除所有"computer science 1"班级的学生文档,可以使用deleteMany
:
|
|
这条命令会查找stus
集合中所有class
字段值为"computer science 1"的文档,并将它们全部删除。
示例3:删除所有文档(慎用)
如果您想要删除集合中的所有文档(但保留集合本身),可以使用不带任何查询条件的deleteMany
(虽然在这种情况下使用drop()
会更快,但drop()
会删除集合及其索引):
|
|
或者,更明确地说,使用{}
作为空查询条件来匹配集合中的所有文档。但请注意,这通常不是推荐的做法,因为它会删除集合中的所有数据。
与Mysql中的
drop
和delete
是一个意思。
注意事项
- 在执行删除操作之前,请确保您确实想要删除这些文档,因为一旦删除,它们就无法通过简单的操作来恢复(除非您有备份)。
- 删除操作是不可逆的,因此请谨慎使用。
- 如果您不确定要删除哪些文档,可以先使用
find
命令来查找匹配的文档,以确保您不会意外删除错误的文档。 - MongoDB的删除操作是原子的,即一旦开始,就不会被其他操作中断。但是,如果多个客户端同时尝试删除相同的文档,则只有一个客户端会成功删除该文档,而其他客户端将不会删除任何文档(因为它们会找不到要删除的文档)。
- 在执行删除操作时,MongoDB会更新集合的元数据,包括文档计数和存储大小等信息。这些更新是自动进行的,您不需要手动干预。
内嵌文档
在MongoDB中,“内嵌文档”(Embedded Document)是一种数据存储方式,允许在一个文档(Document)中包含另一个完整的文档。这种特性使得MongoDB非常适合用于存储具有复杂结构的数据,例如一个用户文档可以内嵌其地址、联系方式、订单历史等多个子文档。
插入内嵌文档
例如,给name="Alice"
的文档添加hobbies
属性,hobbies
也有两个属性:
- cities:beijing、shanghai;
- sports:basketball、tennis。
更新Alice的文档以包含这个新的hobbies
字段:
|
|
此时的hobbies
字段就是一个内嵌文档。
更新后,name="Alice"
的文档内容如下:
|
|
查询内嵌文档的属性
要查询内嵌文档的属性,你可以使用点符号(.
)来访问内嵌文档中的字段。
注意:需要将要查询的属性加上引号(单引号或双引号都可以),否则会报语法错误。
如:找到所有hobbies.cities
数组中包含"beijing"的文档
|
|
数组中添加属性值
$push
是MongoDB中的一个更新操作符,用于向现有的数组字段中添加新元素。如果该字段不存在或不是数组类型,$push
会先将该字段转换为数组,然后添加元素。
$push
操作符不会考虑添加的元素在原数组中是否存在,如果多次执行相同的操作,则会添加重复的元素到数组中。
$addToSet
是 MongoDB 中的一个更新操作符,它用于向文档的一个数组字段中添加一个元素,但前提是该元素尚不存在于数组中。如果元素已经存在于数组中,则不会添加重复项,这样可以确保数组的唯一性。这个操作在处理列表、集合或者需要确保唯一元素的场景下非常有用。
例1:将Alice喜欢的城市添加一个"shenzhen"
|
|
如果执行多次上面的代码,则会添加多次重复的元素。
例2:如果Alice喜欢的城市没有"shenzhen",则添加该城市,否则什么也不做
|
|
如果执行多次上面的代码,也不会添加重复的元素。
例3:从Alice的hobbies.cities
数组中删除刚才添加的"shenzhen"
|
|
这个updateOne
操作会找到name
为"Alice"的第一个文档,并从其hobbies.cities
数组中移除所有值为"shenzhen"的元素。
如果数组中没有"shenzhen",则操作不会有任何效果,也不会报错。如果
hobbies
或cities
字段不存在,MongoDB也不会报错,但同样地,也不会有任何元素被移除。
文档的关系
在MongoDB中,一对一、一对多和多对多关系可以通过不同的方式来实现,主要依赖于文档的嵌入(Embedding)和引用(Referencing)机制。以下是这三种关系在MongoDB中的具体实现方式:
一对一关系
一对一关系在MongoDB中通常通过嵌入文档的方式来实现。即将一个实体的数据嵌入到另一个实体的文档中作为一个字段。这种方式可以减少集合之间的关联查询,提高查询效率。
实现方式:将一个实体的所有信息嵌入到另一个实体的文档中。例如,有两个实体User和Profile,一个User只对应一个Profile,那么可以将Profile的数据嵌入到User的文档中。
优点:数据访问简单,因为所有数据都存储在同一个文档中;查询效率高,因为减少了跨文档的查询。
缺点:如果嵌入的文档过大,可能会导致单个文档超过MongoDB的文档大小限制(16MB)。
一对多关系
一对多关系在MongoDB中可以通过两种方式实现:嵌入文档和引用文档。
嵌入文档方式:将“多”的一方的数据作为数组嵌入到“一”的一方的文档中。然而,这种方法在数据量较大时可能不太适用,因为会导致单个文档过大。
引用文档方式(更常用):在“多”的一方的文档中,使用一个字段来存储“一”的一方的文档的ID(通常是ObjectId)。这样,“一”的一方和“多”的一方分别存储在两个集合中,但可以通过ID进行关联。
实现方式(以用户和订单为例):
- 在users集合中存储用户信息。
- 在orders集合中存储订单信息,每个订单文档中包含一个userid字段,该字段的值指向users集合中对应用户的_id。
优点:数据结构清晰,易于管理;可以在不同集合中分别处理数据,提高系统的灵活性和可扩展性。
缺点:需要进行跨文档的查询来获取完整的数据。
多对多关系
多对多关系在MongoDB中也可以通过两种方式实现:嵌套文档和引用文档。但引用文档的方式更为常见和灵活。
嵌套文档方式:在一个文档中嵌套另一个文档的数组来表示多对多关系。但这种方法在数据量较大时会导致文档过大,不推荐使用。
引用文档方式:创建一个额外的集合来存储两个实体之间的关系。这个集合中的每个文档都包含指向两个实体集合中文档的ID。
实现方式(以学生和课程为例):
- 在students集合中存储学生信息。
- 在courses集合中存储课程信息。
- 创建一个student_courses集合来存储学生和课程之间的关系。每个文档在student_courses集合中都包含student_id和course_id字段,分别指向students和courses集合中的文档。
优点:灵活性强,可以处理复杂的多对多关系;数据易于管理和维护。
缺点:需要进行多次查询来获取完整的数据集。