MongoDB 8.0——数据库操作
1、基础操作
想要掌握数据库,首先需要熟悉如何使用其对数据进行操作。最基础的操作就是数据的创建、查询、修改和删除。MongoDB提供了多种语言支持,例如C#、Go、Java、Motor、Node.js、Perl、PHP、Python、Ruby、Scala等,功能最强大、使用频率最高的方法还是基于mongosh操作数据库。mongosh提供了诸多关于数据库、集合、文档等操作的方法,覆盖内容比较全面。
1.1、操作数据库
mongosh提供了多个有关数据库的方法,下表列举了常用的几个方法。通过这些方法能够获取到数据库的基本信息、统计信息和服务信息等,从整体上掌握数据库的运行状态,也能够进行一些数据操作,例如删除数据库、创建集合或视图等。

1.1.1、查看数据库列表
可以使用如下命令列出当前用户所能看到的数据库列表:
show dbs;
show databases;
1.1.2、创建、切换数据库
在使用任何数据库之前,都要切换到当前数据库下。切换数据库使用use命令,语法如下:
use <databaseName>
如果当前数据库不存在,命令行依然会切换到该数据库,但数据库实际上并不存在。此时,如果对该数据库执行创建集合或插入文档的操作,就能自动创建对应的数据库。
【示例1】创建并切换shopping数据库
首先保证数据库中不存在名为shopping的数据库。执行如下命令,切换到该数据库:
use shopping
命令执行成功后,会提示已切换到数据库shopping,如图所示。

此时,数据库shopping中不存在任何集合和文档,实际上该数据库是不存在的。此时查看数据库列表,如图所示。

在该数据库中执行如下的插入文档命令,该命令会创建一个新的商品集合,名称为products,向商品集合中插入一条数据。
db.products.insertOne({name:"世界经典名著"})
然后执行show dbs查看数据库列表,结果如图所示。

从图中可以看到,此时列表中出现了此前创建的shopping数据库。
1.1.3、删除数据库
在mongosh中,可以使用db.dropDatabase()方法来删除数据库。
【示例2】删除数据库
使用删除命令删除前面创建的shopping数据库。首先保证当前操作在shopping数据库下,如果不在shopping数据库下,则使用use命令进行数据库切换,如图所示。

从图中可以看出,成功删除数据库后,会返回删除结果,ok的值为1,dropped的值为当前删除的数据库的名称。删除完成后,虽然当前命令依然在shopping数据库下,但此时执行show dbs查看数据库列表,可以看到数据库列表中已经不存在shopping数据库了。
1.1.4、创建集合
除直接使用insert命令向不存在的集合中插入文档来创建集合外,也可以通过db.createCollection()方法在当前数据库下直接创建集合,语法如下:
db.createCollection(name, options)
该方法接收两个参数,第一个参数name为集合名称;第二个配置项options为可选项,用来配置集合信息,格式如下:

【示例3】创建并查询集合信息
创建前首先切换到shopping数据库下,执行创建集合的语句。创建完成后,使用db.getCollectionInfos()方法查看集合信息。语句如下:
db.createCollection("products");
show dbs;
执行结果如图所示。

1.1.5、获取集合名称
在mongosh中存在很多方法,这些方法以db.开头,这与操作集合时拥有相同的开头,例如表中提到的db.stats()方法。此时,如果在数据库中创建stats集合,然后使用db.stats开头的插入方法来操作集合数据,会返回一些错误。
【示例4】规避同名方法
首先在数据库中创建stats集合,然后向集合中插入一条数据,命令如下:
db.createCollection("stats");
db.stats.insertOne({total:1000})
执行结果如图所示。

从图中可以看到,命令执行失败,返回了db.stats()没有insertOne方法的提示信息。
此时,可以借助db.getCollection方法来避免这个问题。首先通过命令.editor打开编辑器,在编辑器中编写脚本,定义变量来接收集合名称,然后执行插入操作。命令如下:
var statsColl = db.getCollection("stats");
statsColl .insertOne({total:1000})
执行结果如图所示。

从图中可以看出,插入命令成功执行。
表中剩余的其他方法,此处不再赘述。
1.2、操作集合或视图
MongoDB提供了诸多有关集合或视图操作的方法,常见的方法如表所示。这些方法涵盖文档的查询、插入、更新和删除,集合统计,索引操作等方面。由于篇幅问题,这里只演示常见的一些方法的使用,其余方法可参考MongoDB官方文档。

1.2.1、统计集合或视图的文档数量
MongoDB提供了两个关于统计文档数量的方法:
db.collection.countDocuments(query, options)
db.collection.estimatedDocumentCount(options)
countDocuments(query, options)是通过实际存储的数据,利用 s u m 表达式和 sum表达式和 sum表达式和group聚合阶段进行统计计算的。此方法会扫描整个集合,因此存在一定性能风险。在没有精确的过滤条件时,该方法的查询效率较低。
其中,query参数为查询条件,如果查询所有的数据,则该参数传入空值。options参数为可选参数,可接受以下4个值。
limit:返回文档数量的最大值,一般用于分页。skip:查询时跳过的文档数量,一般用于分页。hint:查询所使用的索引名称或规则,用于提高查询效率。maxTimeMS:查询语句运行的最大限制时间,防止因查询时间过长造成数据库服务崩溃或执行性能极差的查询语句。
而estimatedDocumentCount(options)是通过元数据来统计文档数量的,因此在查询整个集合时,该方法的效率更高。它仅接收一个参数,即maxTimeMS(最大查询时间限制值)。但需要注意的是,这个方法在特定情况下可能与实际文档数量存在差异,例如在意外的实例停机或孤儿文档的情况下,可能会导致统计数据不一致。
【示例5】对比两个统计方法的查询效率
通过脚本来计算执行两个统计方法所需的时间,对比两个方法的查询效率。脚本内容如下:
// 设置开始时间
var startTime = new Date();
// 执行查询操作
var result = db.products.countDocuments({});
// 设置结束时间
var endTime = new Date();
// 计算查询所需的时间(单位为毫秒)
var queryTime = endTime - startTime;
print("countDocuments所需时间:" + queryTime + "ms");
// 设置开始时间
var startTime2 = new Date();
// 执行查询操作
var result2 = db.products.estimatedDocumentCount();
// 设置结束时间
var endTime2 = new Date();
// 计算查询所需的时间(单位为毫秒)
var queryTime2 = endTime2 - startTime2;
print("estimatedDocumentCount所需时间:" + queryTime2 + "ms");
执行结果如图所示。在图中可以看到,在数据量小时,两个方法的差异不太大,但已有明显差异,随着数据量的增长,该差距会越来越大。因此,在实际应用中,要根据具体场景选择更适合的方法。

1.2.2、批量操作bulkWrite
批量写入方法格式如下:

- 第一个参数为operations,即操作名称的数组集合,可支持的操作有insertOne、updateOne、updateMany、deleteOne、deleteMany、replaceOne。
- 第二个参数为writeConcern,即相关文档,该参数为可选参数。如果不传入的话,会使用默认值,路径为/includes/extracts/transactions-operations-write-concern.rst。注意,如果在事务中运行该方法,则不能传递writeConcern参数。
- 第三个参数为ordered,布尔型可选参数,指明数据库是否按顺序执行操作,默认为true。
(1)使用bulkWrite插入数据。
语法格式如下:

【示例6】使用bulkWrite方法插入数据
向shopping数据库的products中插入图书商品信息。语句如下:
db.products.bulkWrite([
{insertOne: {"document": {"name": "活着", "author": "余华", "press": "北京十月文艺出版社", "publishTime":"2021-10-01", "type": "book", "comments": "1332375", "price": 45, "discount": 31, "ebookPrice": 29}}}
])
执行结果如图所示。

其中,document是insertOne方法的第一个参数。
(2)使用bulkWrite方法更新数据
bulkWrite方法支持更新单个文档和更新多个文档,语法格式如下:

以上方法涉及的参数与db.collection.updateOne()、db.collection.updateMany()方法的参数相同。参数描述如下。
filter:查询条件,与db.collection.find()中支持的过滤条件相同。update:更新操作,支持文档或聚合管道。upsert:可选参数。是否支持upsert(更新插入操作),即如果表中已经存在指定值,则更新现有行;如果指定值不存在,则插入新行。默认情况下,该值为false。arrayFilters:可选参数。一组筛选文档,用于确定要为数组字段的更新操作修改哪些数组元素。collation:可选参数。指定排序规则。hint:可选参数。用于支持过滤条件的索引。如果指定的索引不存在,则操作将出错。
(3)使用bulkWrite替换数据。
使用replaceOne方法实现数据替换,语句如下:

(4)使用bulkWrite删除数据。
bulkWrite支持删除单个文档和删除多个文档的操作,具体语法如下:

(5)批量操作。
如果在统一操作中,同时进行插入数据、更新数据、删除数据的操作,可以用如下形式:

默认情况下,会从第一条语句按顺序向后依次执行。如果设置排序为否,即不按顺序执行,MongoDB会根据性能自行将语句重新排序,最终执行结果与顺序执行有可能不同。
【示例7】批量操作商品数据
把商品集合中出版社为“北京十月文艺出版社”的书,折扣价减3,然后给目前为book的文档增加字段“字数”,字段名为words。
在操作前先插入一些数据,数据内容如图所示。

接下来执行批量操作语句,语句如下:

执行结果如图所示。

然后再次查询上述数据,与操作之前对比,可以发现数据已被修改,如图所示。

1.3、操作文档
MongoDB支持多种语言或工具操作数据库,例如MongoDB Shell、Compass、C#、Go、Java、Motor、Node.js、Perl、PHP、Python、Ruby、Scala。
在以上所支持的语言中,最常用且应用最广泛的是MongoDB Shell,也就是mongosh。
1.3.1、插入文档
插入文档包含插入单个和插入多个文档两种方式。具体使用方法可以参考链接。
1.3.2、查询文档
查询文档需要指定查询谓词,以表示要返回的文档。如果指定空查询谓词({ }),则查询将返回集合中的所有文档。
2、文本搜索
MongoDB提供了多种不同的文本搜索策略,这取决于数据是存储于云上还是本地库上。对于MongoDB Atlas上托管的数据执行文本搜索,可以使用MongoDB AtlasSearch。Atlas Search支持细粒度文本索引和丰富的查询语言,可快速获取相关搜索结果。
对于返回的Atlas搜索结果或元数据,Atlas查询使用聚合管道阶段,且管道阶段有$search和$searchMeta两种形式。基于所选择的管道阶段,返回全文检索的查询结果或有关数据结果的元数据。
$search:返回全文检索的查询结果。$searchMeta:返回有关数据结果的元数据。
$search管道阶段采用的是全文检索的方式,检索时基于 指定的字段,这些字段必须包含在Atlas查询索引中。语法形式如下:

参数说明如表所示。

聚合管道支持的操作器名称和收集器名称如表所示。

Atlas搜索目前仅有一个收集器Facet,该收集器会按指定的分面(Facet)字段中的值或范围对查询结果进行分组,并返回每个分组的计数。
3、地理空间查询
MongoDB支持地理空间查询,允许用户存储地理空间数据并对这些数据进行查询。MongoDB使用两种数据类型来存储地理空间数据:GeoJSON对象和传统坐标对。在地理空间查询中,MongoDB支持两种索引:2dsphere和2d索引。
3.1、GeoJSON对象
GeoJSON数据可以用来指定点、线和多边形等。它使用内嵌文档来指定,包含type和coordinates两个字段。type指定GeoJSON对象类型,coordinates指定对象的坐标。格式如下:
<field>: { type: <GeoJSON type> , coordinates: <coordinates> }
注意:有效经度值介于-180 ~ 180,有效纬度值介于-90 ~ 90。
MongoDB支持的GeoJSON对象的类型如表所示。

例如,指定一个点,语句如下:

MongoDB在使用GeoJSON对象进行地理空间查询时,是基于球面来计算的。
3.2、传统坐标对
MongoDB在使用传统坐标对进行距离计算时,是基于几何平面来计算的。
这种计算方式支持的索引是2d索引。如果手动将数据转换为GeoJSON Point类型,MongoDB通过使用2dsphere索引支持在传统坐标对上进行球面计算。
传统坐标对有两种方式存储经纬度,即数组和嵌入文档。
数组形式格式如下:
<field>: [ <x>, <y> ]
如果是经纬度数据,那么格式如下:
<field>: [<longitude>, <latitude> ]
相对应的内嵌文档格式如下:
<field>: { <field1>: <x>, <field2>: <y> }
<field>: { <field1>: <longitude>, <field2>: <latitude> }
3.3、2dsphere索引
2dsphere索引用于支持地球表面的球面计算。
3.3.1、创建索引
语法如下:
db.collection.createIndex( { <location field> : "2dsphere" } )
<location field>字段对应的值为GeoJSON对象或传统坐标对。
例如,在数据库中创建一个地点集合,在集合中存入一些地点数据,如下所示:

接下来在地点数据下创建2dsphere索引,如下所示:
db.places.createIndex({ loc : "2dsphere" })
使用2dsphere可以进行多种类型的查询,例如查询多边形内的位置、查询球面位置、查询与GeoJSON对象相交的位置、查询球面上圆内的位置。
3.3.2、查询多边形内的位置
语法如下:

使用的操作符为$geoWithin,该操作符对索引无要求,但索引会提高查询性能。location field对应的字段必须为GeoJSON格式的数据。coordinates为多边形的坐标对数据,且第一个点与最后一个点的坐标必须相同。
例如,查询在如下多边形范围内的数据:
db.places.find({
loc: {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: [[
[116.44, 39.96],
[116.49, 39.96],
[116.48, 39.82],
[116.43, 39.90],
[116.44, 39.96]
]]
}
}
}
})
执行结果如下所示。
[
{
_id: ObjectId('6a6db72da80522945cf824f1'),
loc: { type: 'Point', coordinates: [ 116.45, 39.92 ] },
name: 'Ritan Park',
category: 'Park'
}
]
3.3.3、查询球面上某个点附近的位置
语法如下:

使用的操作符为$near。type为Point,coordinates为点的经纬度。$maxDistance为查询的距离范围。
下面来看一个示例,查询在点[116.42,39.92]附近500米的位置。
db.places.find({
loc: {
$near: {
$geometry: {
type: "Point",
coordinates: [116.42, 39.92]
},
$maxDistance: 500
}
}
})
执行结果如下所示。
[
{
_id: ObjectId('6a6db72da80522945cf824f3'),
loc: { type: 'Point', coordinates: [ 116.42, 39.92 ] },
name: 'Xiehe Hospital',
category: 'Hospital'
}
]
如果有多个结果,则查询结果会由近到远排列。
3.3.4、
语法如下:

在上面的语句中,$geoIntersects操作符对索引没有强制要求,但使用索引可以提高查询性能。type为GeoJSON对象的数据类型,而coordinates为GeoJSON对象的数据值。
下面来看一个使用场景的示例:在路线上寻找加油站。
使用数据格式为LineString的GeoJSON对象存储路线,然后查询该路线上的加油站。
首先,插入加油站数据,命令如下:
db.gasStations.insertMany([
{
loc: {type: "Point", coordinates: [116.43, 39.91]},
province: "Beijing",
city: "Beijing",
name: "Sinopec Gas Station"
},
{
loc: {type: "Point", coordinates: [116.68, 39.55]},
province: "Hebei",
city: "Langfang",
name: "CNPC Gas Station"
},
{
loc: {type: "Point", coordinates: [117.97, 37.43]},
province: "Shandong",
city: "Binzhou",
name: "Chambroad Gas Station"
}
])
插入结果如下所示。
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6a6dc472a80522945cf824f4'),
'1': ObjectId('6a6dc472a80522945cf824f5'),
'2': ObjectId('6a6dc472a80522945cf824f6')
}
}
接下来查询路线上的加油站,命令如下:
db.gasStations.find({
loc: {
$geoIntersects: {
$geometry: {
type: "LineString",
coordinates: [
[116.42, 39.91],
[116.43, 39.91],
[116.44, 39.91],
[116.45, 39.91]
]
}
}
}
})
coordinates为路线的经纬度数据。
查询结果如下所示。
[
{
_id: ObjectId('6a6dc472a80522945cf824f4'),
loc: { type: 'Point', coordinates: [ 116.43, 39.91 ] },
province: 'Beijing',
city: 'Beijing',
name: 'Sinopec Gas Station'
}
]
该路线上有一个加油站数据被查出。
3.3.5、查询球面上圆内的位置
查询球面上圆内的位置集合,需要使用$geoWithin结合$centerSphere操作符。在$centerSphere参数对象中指定圆的经纬度和半径。语法如下:

需要注意,radius参数代表的是带弧度的半径。因此,需要将参数转换为球面操作符的半径。转换时一般涉及英里与千米两种距离单位。
- 英里:使用英里除以3953.2。
- 千米:使用千米除以6378.1。
在前面插入places集合的数据基础上,查询距离点[116.42, 39.92]附近5千米以内的地点。
db.places.find({
loc: {
$geoWithin: {
$centerSphere: [
[116.42, 39.92], 5 / 6378.1
]
}
}
})
查询结果如下所示。
[
{
_id: ObjectId('6a6db72da80522945cf824f1'),
loc: { type: 'Point', coordinates: [ 116.45, 39.92 ] },
name: 'Ritan Park',
category: 'Park'
},
{
_id: ObjectId('6a6db72da80522945cf824f3'),
loc: { type: 'Point', coordinates: [ 116.42, 39.92 ] },
name: 'Xiehe Hospital',
category: 'Hospital'
}
]
3.4、2d索引
2d索引用于非球面的二维平面。在使用2d索引时,虽然可以通过$nearSphere查询来实现球面计算,但是在进行球面查询时,还是推荐使用2dsphere索引进行替换。
创建2d索引使用如下语法:
db.collection.createIndex( { <location field> : "2d" } )
<location field>字段对应的值为合法的坐标对。
在创建2d索引前,先在集合中插入数据,如下所示:
db.contacts.insertMany([
{
name: "Tencent",
phone: "400-700-700",
address: [113.93, 22.54]
},
{
name: "Baidu",
phone: "400-800-8888",
address: [116.31, 40.06]
},
{
name: "Alibaba",
phone: "400-800-1688",
address: [116.49, 40.03]
}
])
其中,address数据为坐标对数据,在address字段上创建2d索引,如下所示:
db.contacts.createIndex({ address : "2d" })
接下来,使用$near操作符对坐标进行查询。查询语法如下:

longitude:代表经度,范围为-180~180。latitude:代表纬度,范围为-90~90。
$maxDistance代表距离范围,即距离上述坐标在该范围内的数据,距离单位为米。查询结果将由近到远排列。
在前面创建的数据与索引基础上,查询在点[ 116.48,40.02 ]附近5米以内的地点,可以使用如下语句:
db.contacts.find({
address: {
$near: [116.48, 40.02],
$maxDistance: 5
}
})
执行结果如下所示。
[
{
_id: ObjectId('6a6dc87aa80522945cf824f9'),
name: 'Alibaba',
phone: '400-800-1688',
address: [ 116.49, 40.03 ]
},
{
_id: ObjectId('6a6dc87aa80522945cf824f8'),
name: 'Baidu',
phone: '400-800-8888',
address: [ 116.31, 40.06 ]
}
]
可以看出,在二维平面上,只能按照坐标系进行直线距离计算。
4、常用的操作符
MongoDB提供了非常全面且强大的操作符和修饰符,通过这些操作符和修饰符可以实现对数据的各种操作。MongoDB的操作符与修饰符主要分为4类:查询和投影操作符、更新操作符、聚合管道操作符和查询修饰符。
4.1、查询和投影操作符
4.1.1、比较查询操作符
比较查询操作符是查询中最常用的操作符。在前面的讲解与示例中已多次用到相关的操作符,尤其是find查询中的相等判断。
例如,使用$eq操作符查询书籍中书名为《三国演义》的图书信息,可使用如下命令:
db.books.find({title:{$eq:'三国演义'}})
该语句等同于如下语句:
db.books.find({title:'三国演义'})
查询结果如图所示。

$eq操作符也可以进行数组操作,例如查询类别为历史类长篇小说的图书,即类别中同时含有Novel和History的图书。命令如下:
db.books.find({genres:{$eq:['Novel','History']}})
该语句等同于如下语句:
db.books.find({genres:['Novel','History']})
查询结果如图所示。

常用的比较查询操作符如表所示。

4.1.2、逻辑查询操作符
逻辑查询操作符的使用频率非常高。常用的逻辑查询操作符如表所示。

4.1.3、元素查询操作符
常用的元素查询操作符如表所示。

下面的代码示例通过$exists操作符判断title字段是否存在:
db.books.find({ title: { $exists: true } })
4.1.4、计算查询操作符
常用的计算查询操作符如表所示。

下面的代码示例先在集合的title字段上创建索引,然后使用$text操作符指定$search操作符的查询字符串:
db.books.createIndex( { title: "text" } )
db.books.find({ $text: { $search: “三国”} })
4.1.5、地理空间查询运算符
常用的地理空间查询运算符如表所示。

4.1.6、查询操作符数组
常用的查询操作符数组如表所示。

下面的代码示例查询匹配的文档,这些文档的results数组中至少包含一个大于或等于80且小于85的元素:

4.1.7、投影操作符
常用的投影操作符如表所示。

以下操作使用results数组上的$slice投影运算符来返回数组及其前3个元素。如果数组的元素少于3个,则返回数组中的所有元素。
下面的代码示例通过在results数组上使用$slice投影运算符来返回数组的前3个元素,如果数组的元素少于3个,则返回数组中的所有元素:
db.scores.find( {}, { results: { $slice: 3 } } )
4.2、更新操作符
根据操作对象划分,更新操作符分为字段更新操作符、数组更新操作符、数组更新修饰符、位更新操作符和隔离更新操作符。
4.2.1、字段更新操作符
常用的字段更新操作符如表所示。

下面的代码示例通过使用$rename运算符将title字段重命名为newtitle字段:

4.2.2、数组更新操作符
常用的数组更新操作符如表所示。

下面的代码示例通过使用$addToSet运算符为results字段添加新数据项:

4.2.3、数组更新修饰符
常用的数组更新修饰符如表所示。

下面的代码示例通过使用$sort运算符为results字段重新进行排序:

4.2.4、位更新操作符
常用的位更新操作符如表所示。

4.2.5、隔离更新操作符
常用的隔离更新操作符如表所示。

4.3、聚合管道操作符
在复杂查询中,聚合管道查询的应用较多。常用的聚合管道操作符如表所示。

下面的代码示例通过使用$match运算符匹配author字段值的全部文档:

4.4、查询修饰符
在查询时,除操作符外,还有一些修饰符,用来指定一些限制条件或格式。常用的查询修饰符如表所示。

下面的代码示例通过使用$comment运算符为查询结果添加评论信息:

以上为MongoDB常用的操作符与修饰符描述。由于篇幅有限,不再一一列举,可访问官网手册地址https://www.mongodb.com/docs/v3.0/reference/operator/,查看详细的操作符与修饰符列表,了解每一个符号的含义与使用。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)