跳至主要內容

MongoDB速查

大约 44 分钟约 13076 字

认识MongoDB

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

安装MongoDB

Windows系统:访问https://www.mongodb.com/try/download/communityopen in new window下载与Windows系统对应的.msi文件。

我在学习MongoDB的过程中使用了更为方便的安装方式即使用docker安装。下方为使用docker下载和启动容器的方法:

docker pull mongo
docker run --name mongodb --restart=always -p 27017:27017 -d mongo

到这里MongoDB就已经成功运行了。它默认是没有用户名和密码的,因此使用DataGrip直接连接即可。

docker ps

这个步骤的意义是获取该容器的CONTAINER ID,以便于下一步进入容器内执行MongoDB代码。

docker exec -it <CONTAINER ID/NAME> mongosh

之后就可以进入mongoDB的命令行了,如下图。

MongoDB控制台
MongoDB控制台

这时候就可以执行mongoDB的语句了,如使用show dbs查看所有数据库。

运行结果
运行结果

接下来就是为数据库登录设置用户名和密码。

db.createUser({user:'admin',pwd:'123456',roles:[{ role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});
db.auth('admin', '123456')
设置用户名和密码
设置用户名和密码

MongoDB和SQL

下表对比了MongoDB和SQL的差异。

SQL术语/概念MongoDB术语/概念解释/说明
databasedatabase数据库
tablecollection数据库表/集合
rowdocument数据记录行/文档
columnfield数据字段/域
indexindex索引
table joins表连接,MongoDB不支持
primary keyprimary key主键,MongoDB自动将_id字段设置为主键

数据库

一个mongodb中可以建立多个数据库。

MongoDB的默认数据库为"db",该数据库存储在data目录中。

MongoDB的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。

show dbs可以查看所有数据库。

执行db 命令可以显示当前数据库对象或集合。

运行use命令,可以连接到一个指定的数据库。

运行结果
运行结果

数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串。

  • 不能是空字符串("")。
  • 不得含有' '(空格)、.、$、/、\和\0 (空字符)。
  • 应全部小写。
  • 最多64字节。

有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。

  • admin:从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
  • local:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
  • config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。

文档是一组键值(key-value)对(即 BSON)。MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大的区别,也是 MongoDB 非常突出的特点。

一个简单的文档例子如下:

{"site":"www.runoob.com", "name":"菜鸟教程"}

下表列出了 RDBMS 与 MongoDB 对应的术语:

RDBMSMongoDB
数据库数据库
表格集合
文档
字段
表联合嵌入文档
主键主键 (MongoDB 提供了 key 为 _id )

下表列出了其他数据库服务器与客户端和MongoDB的对比。

其他数据库MongoDB数据库
Mysqld/Oraclemongodb
mysql/sqlplusmongo

需要注意的是:

  1. 文档中的键/值对是有序的。
  2. 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个嵌入的文档)。
  3. MongoDB区分类型和大小写。
  4. MongoDB的文档不能有重复的键。
  5. 文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。

文档键命名规范:

  • 键不能含有\0 (空字符)。这个字符用来表示键的结尾。
  • .和$有特别的意义,只有在特定环境下才能使用。
  • 以下划线"_"开头的键是保留的(不是严格要求的)。

集合

集合就是 MongoDB 文档组,类似于 RDBMS (关系数据库管理系统:Relational Database Management System)中的表格。

集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。

比如,我们可以将以下不同数据结构的文档插入到集合中:

{"site":"www.baidu.com"}
{"site":"www.google.com","name":"Google"}
{"site":"www.runoob.com","name":"菜鸟教程","num":5}

当第一个文档插入时,集合就会被创建。

合法的集合名

  • 集合名不能是空字符串""。
  • 集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
  • 集合名不能以"system."开头,这是为系统集合保留的前缀。
  • 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。

如下实例:

db.col.findOne()

capped collections

Capped collections 就是固定大小的collection。

它有很高的性能以及队列过期的特性(过期按照插入的顺序). 有点和 "RRD" 概念类似。

Capped collections 是高性能自动的维护对象的插入顺序。它非常适合类似记录日志的功能和标准的 collection 不同,你必须要显式的创建一个capped collection,指定一个 collection 的大小,单位是字节。collection 的数据存储空间值提前分配的。

Capped collections 可以按照文档的插入顺序保存到集合中,而且这些文档在磁盘上存放位置也是按照插入顺序来保存的,所以当我们更新Capped collections 中文档的时候,更新后的文档不可以超过之前文档的大小,这样话就可以确保所有文档在磁盘上的位置一直保持不变。

由于 Capped collection 是按照文档的插入顺序而不是使用索引确定插入位置,这样的话可以提高增添数据的效率。MongoDB 的操作日志文件 oplog.rsopen in new window 就是利用 Capped Collection 来实现的。

要注意的是指定的存储大小包含了数据库的头信息。

db.createCollection("mycoll", {capped:true, size:100000})
  • 在 capped collection 中,你能添加新的对象。
  • 能进行更新,然而,对象不会增加存储空间。如果增加,更新就会失败 。
  • 使用 Capped Collection 不能删除一个文档,可以使用 drop() 方法删除 collection 所有的行。
  • 删除之后,你必须显式的重新创建这个 collection。
  • 在32bit机器中,capped collection 最大存储为 1e9( 1X109)个字节。

元数据

数据库的信息是存储在集合中。它们使用了系统的命名空间:

dbname.system.*

在MongoDB数据库中名字空间 <dbname>.system.* 是包含多种系统信息的特殊集合(Collection),如下:

集合命名空间描述
dbname.system.namespaces列出所有名字空间。
dbname.system.indexes列出所有索引。
dbname.system.profile包含数据库概要(profile)信息。
dbname.system.users列出所有可访问数据库的用户。
dbname.local.sources包含复制对端(slave)的服务器信息和状态。

对于修改系统集合中的对象有如下限制。

{{system.indexes}}插入数据,可以创建索引。但除此之外该表信息是不可变的(特殊的drop index命令将自动更新相关信息)。

{{system.users}}是可修改的。 {{system.profile}}是可删除的。

MongoDB 数据类型

下表为MongoDB中常用的几种数据类型。

数据类型描述
String字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。
Integer整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。
Boolean布尔值。用于存储布尔值(真/假)。
Double双精度浮点值。用于存储浮点值。
Min/Max keys将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。
Array用于将数组或列表或多个值存储为一个键。
Timestamp时间戳。记录文档修改或添加的具体时间。
Object用于内嵌文档。
Null用于创建空值。
Symbol符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。
Date日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。
Object ID对象 ID。用于创建文档的 ID。
Binary Data二进制数据。用于存储二进制数据。
Code代码类型。用于在文档中存储 JavaScript 代码。
Regular expression正则表达式类型。用于存储正则表达式。

下面说明下几种重要的数据类型。

ObjectId

ObjectId 类似唯一主键,可以很快的去生成和排序,包含 12 bytes,含义是:

  • 前 4 个字节表示创建 unix 时间戳,格林尼治时间 UTC 时间,比北京时间晚了 8 个小时
  • 接下来的 3 个字节是机器标识码
  • 紧接的两个字节由进程 id 组成 PID
  • 最后三个字节是随机数
ObjectID各位置意义
ObjectID各位置意义

MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象

由于 ObjectId 中保存了创建的时间戳,所以你不需要为你的文档保存时间戳字段,你可以通过 getTimestamp 函数来获取文档的创建时间:

test> newObject.getTimestamp()
ISODate("2023-07-25T11:44:15.000Z")

ObjectId 转为字符串

test> newObject.toString()
64bfb58f16296c9adcfc3a77

字符串

BSON 字符串都是 UTF-8 编码。

时间戳

BSON 有一个特殊的时间戳类型用于 MongoDB 内部使用,与普通的 日期 类型不相关。 时间戳值是一个 64 位的值。其中:

  • 前32位是一个 time_t 值(与Unix新纪元相差的秒数)
  • 后32位是在某秒中操作的一个递增的序数

在单个 mongod 实例中,时间戳值通常是唯一的。

在复制集中, oplog 有一个 ts 字段。这个字段中的值使用BSON时间戳表示了操作时间。

BSON 时间戳类型主要用于 MongoDB 内部使用。在大多数情况下的应用开发中,你可以使用 BSON 日期类型。

日期

表示当前距离 Unix新纪元(1970年1月1日)的毫秒数。日期类型是有符号的, 负数表示 1970 年之前的日期。

test> var mydate1 = new Date()

test> mydate1
ISODate("2023-07-25T11:48:35.313Z")
test> typeof mydate1
object
test> var mydate2 = ISODate()

test> mydate2
ISODate("2023-07-25T11:49:03.635Z")
test> typeof mydate2
object
运行结果
运行结果

这样创建的时间是日期类型,可以使用 JS 中的 Date 类型的方法。

返回一个时间类型的字符串:

test> var mydate1str = mydate1.toString()

test> mydate1str
Tue Jul 25 2023 11:48:35 GMT+0000 (Coordinated Universal Time)
test> typeof mydate1str
string
运行结果
运行结果

或者

test> Date()
Tue Jul 25 2023 11:50:26 GMT+0000 (Coordinated Universal Time)
运行结果
运行结果

MongoDB - 连接

在本教程我们将讨论 MongoDB 的不同连接方式。

启动 MongoDB 服务

在前面的教程中,我们已经讨论了如何启动 MongoDB 服务open in new window,你只需要在 MongoDB 安装目录的 bin 目录下执行 mongodb 即可。

执行启动操作后,mongodb 在输出一些必要信息后不会输出任何信息,之后就等待连接的建立,当连接被建立后,就会开始打印日志信息。

你可以使用 MongoDB shell 来连接 MongoDB 服务器。你也可以使用 PHP 来连接 MongoDB。本教程我们会使用 MongoDB shell 来连接 Mongodb 服务,之后的章节我们将会介绍如何通过php 来连接MongoDB服务。

标准 URI 连接语法:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
  • mongodb:// 这是固定的格式,必须要指定。
  • username:password@ 可选项,如果设置,在连接数据库服务器之后,驱动都会尝试登录这个数据库
  • host1 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址。
  • portX 可选的指定端口,如果不填,默认为27017
  • /database 如果指定username:password@,连接并验证登录指定数据库。若不指定,默认打开 test 数据库。
  • ?options 是连接选项。如果不使用/database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开

标准的连接格式包含了多个选项(options),如下所示:

选项描述
replicaSet=name验证replica set的名称。 Impliesconnect=replicaSet.
slaveOk=true|falsetrue:在connect=direct模式下,驱动会连接第一台机器,即使这台服务器不是主。在connect=replicaSet模式下,驱动会发送所有的写请求到主并且把读取操作分布在其他从服务器。false: 在 connect=direct模式下,驱动会自动找寻主服务器. 在connect=replicaSet 模式下,驱动仅仅连接主服务器,并且所有的读写命令都连接到主服务器。
safe=true|falsetrue: 在执行更新操作之后,驱动都会发送getLastError命令来确保更新成功。(还要参考 wtimeoutMS).false: 在每次更新之后,驱动不会发送getLastError来确保更新成功。
w=n驱动添加 { w : n } 到getLastError命令. 应用于safe=true。
wtimeoutMS=ms驱动添加 { wtimeout : ms } 到 getlasterror 命令. 应用于 safe=true.
fsync=true|falsetrue: 驱动添加 { fsync : true } 到 getlasterror 命令.应用于 safe=true.false: 驱动不会添加到getLastError命令中。
journal=true|false如果设置为 true, 同步到 journal (在提交到数据库前写入到实体中). 应用于 safe=true
connectTimeoutMS=ms可以打开连接的时间。
socketTimeoutMS=ms发送和接受sockets的时间。

实例

使用默认端口来连接 MongoDB 的服务。

mongodb://localhost

通过 shell 连接 MongoDB 服务:

$ ./mongo
MongoDB shell version: 4.0.9
connecting to: test
... 

这时候你返回查看运行 ./mongod 命令的窗口,可以看到是从哪里连接到MongoDB的服务器,您可以看到如下信息:

……省略信息……
2015-09-25T17:22:27.336+0800 I CONTROL  [initandlisten] allocator: tcmalloc
2015-09-25T17:22:27.336+0800 I CONTROL  [initandlisten] options: { storage: { dbPath: "/data/db" } }
2015-09-25T17:22:27.350+0800 I NETWORK  [initandlisten] waiting for connections on port 27017
2015-09-25T17:22:36.012+0800 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:37310 #1 (1 connection now open)  # 该行表明一个来自本机的连接

……省略信息……

MongoDB 连接命令格式

使用用户名和密码连接到 MongoDB 服务器,你必须使用 'username:password@hostname/dbname' 格式,'username'为用户名,'password' 为密码。

使用用户名和密码连接登录到默认数据库:

$ ./mongo
MongoDB shell version: 4.0.9
connecting to: test

使用用户 admin 使用密码 123456 连接到本地的 MongoDB 服务上。输出结果如下所示:

> mongodb://admin:123456@localhost/
... 

使用用户名和密码连接登录到指定数据库,格式如下:

mongodb://localhost/test

更多连接实例

连接本地数据库服务器,端口是默认的。

mongodb://localhost

使用用户名fred,密码foobar登录localhost的admin数据库。

mongodb://localhost

使用用户名fred,密码foobar登录localhost的baz数据库。

mongodb://localhost/baz

连接 replica pair, 服务器1为example1.com服务器2为example2。

mongodb://example1.com:27017,example2.com:27017

连接 replica set 三台服务器 (端口 27017, 27018, 和27019):

mongodb://localhost,localhost:27018,localhost:27019

连接 replica set 三台服务器, 写入操作应用在主服务器 并且分布查询到从服务器。

mongodb://host1,host2,host3/?slaveOk=true

直接连接第一个服务器,无论是replica set一部分或者主服务器或者从服务器。

mongodb://host1,host2,host3/?connect=direct;slaveOk=true

当你的连接服务器有优先级,还需要列出所有服务器,你可以使用上述连接方式。

安全模式连接到localhost:

mongodb://localhost/?safe=true

以安全模式连接到replica set,并且等待至少两个复制服务器成功写入,超时时间设置为2秒。

mongodb://host1,host2,host3/?safe=true;w=2;wtimeoutMS=2000

MongoDB 创建数据库

语法

MongoDB 创建数据库的语法格式如下:

use DATABASE_NAME

如果数据库不存在,则创建数据库,否则切换到指定数据库。

实例

以下实例我们创建了数据库 qi:

test> use qi
switched to db qi
qi> db
qi
创建数据库
创建数据库

如果你想查看所有数据库,可以使用 show dbs 命令:

qi> show dbs
admin   132.00 KiB
config  108.00 KiB
local    72.00 KiB
查看所有数据库
查看所有数据库

可以看到,我们刚创建的数据库 qi 并不在数据库的列表中, 要显示它,我们需要向 qi 数据库插入一些数据。

qi> db.qi.insertOne({'name': 'qi'})
{
  acknowledged: true,
  insertedId: ObjectId("64bfb7cf16296c9adcfc3a78")
}
qi> show dbs
admin   132.00 KiB
config  108.00 KiB
local    72.00 KiB
qi       40.00 KiB
插入数据
插入数据

MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。

注意: 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。

MongoDB 删除数据库

语法

MongoDB 删除数据库的语法格式如下:

db.dropDatabase()

删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。

实例

以下实例我们删除了数据库 qi。

首先,查看所有数据库:

qi> show dbs
admin   132.00 KiB
config  108.00 KiB
local    72.00 KiB
qi       40.00 KiB

接下来我们切换到数据库 runoob:

qi> use qi
switched to db qi

执行删除命令:

qi> db.dropDatabase()
{ "dropped" : "runoob", "ok" : 1 }

最后,我们再通过 show dbs 命令数据库是否删除成功:

qi> show dbs
admin   132.00 KiB
config  108.00 KiB
local    72.00 KiB
运行结果
运行结果

删除集合

集合删除语法格式如下:

db.collection.drop()

以下实例删除了 qi 数据库中的集合 qi:

qi> use qi
already on db qi
qi> db.createCollection('qi')
{ ok: 1 }
qi> show tables
qi
qi> db.qi.drop()
true
qi> show tables

运行结果
运行结果

MongoDB 创建集合

本章节我们为大家介绍如何使用 MongoDB 来创建集合。

MongoDB 中使用 createCollection() 方法来创建集合。

语法格式:

db.createCollection(name, options)

参数说明:

  • name: 要创建的集合名称
  • options: 可选参数, 指定有关内存大小及索引的选项

options 可以是如下参数:

字段类型描述
capped布尔(可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。 当该值为 true 时,必须指定 size 参数。
autoIndexId布尔3.2 之后不再支持该参数。(可选)如为 true,自动在 _id 字段创建索引。默认为 false。
size数值(可选)为固定集合指定一个最大值,即字节数。 如果 capped 为 true,也需要指定该字段。
max数值(可选)指定固定集合中包含文档的最大数量。

在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。

实例

在 test 数据库中创建 qi 集合:

qi> use test
switched to db test
test> db.createCollection('qi')
{ ok: 1 }
test>

如果要查看已有集合,可以使用 show collectionsshow tables 命令:

test> show tables
qi
test> show collections
qi

下面是带有几个关键参数的 createCollection() 的用法:

创建固定集合 mycol,整个集合空间大小 6142800 B, 文档最大个数为 10000 个。

db.createCollection("mycol", {capped: true, autoIndexId: true, size: 6142800, max: 10000 })

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。

test> show collections
mycol
qi

MongoDB 删除集合

本章节我们为大家介绍如何使用 MongoDB 来删除集合。

MongoDB 中使用 drop() 方法来删除集合。

语法格式:

db.collection.drop()

返回值

如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。

实例

在数据库 test 中,我们可以先通过 show collections 命令查看已存在的集合:

test> show collections
mycol
qi

接着删除集合 mycol :

test> db.mycol.drop()
true

通过 show collections 再次查看数据库 test 中的集合:

test> show collections
qi
test>

从结果中可以看出 mycol 集合已被删除。

删除集合
删除集合

MongoDB文档的增、删、改

本章节中我们将向大家介绍如何将数据插入到 MongoDB 的集合中。

文档的数据结构和 JSON 基本一样。

所有存储在集合中的数据都是 BSON 格式。

BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。

MongoDB插入文档

MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:

db.COLLECTION_NAME.insert(document)
db.COLLECTION_NAME.save(document)
  • save():如果 _id 主键存在则更新数据,如果不存在就插入数据。该方法新版本中已废弃,可以使用 db.collection.insertOne()db.collection.replaceOne() 来代替。
  • insert(): 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。该方法也被废弃,推荐使用下方的两种方法。
插入文档
插入文档

3.2 版本之后新增了 db.collection.insertOne() 和 db.collection.insertMany()。

db.collection.insertOne() 用于向集合插入一个新文档,语法格式如下:

db.collection.insertOne(
   <document>,
   {
      writeConcern: <document>
   }
)

db.collection.insertMany() 用于向集合插入一个多个文档,语法格式如下:

db.collection.insertMany(
   [ <document 1> , <document 2>, ... ],
   {
      writeConcern: <document>,
      ordered: <boolean>
   }
)

参数说明:

  • document:要写入的文档。
  • writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
  • ordered:指定是否按顺序写入,默认 true,按顺序写入。

示例如下。

  • db.collection.insertOne():向指定集合中插入一条文档数据。
test> var document = db.test.insertOne({'key': 'value'})

test> document
{
  acknowledged: true,
  insertedId: ObjectId("64c8889d5a6d80b51405549d")
}
运行结果
运行结果
  • db.collection.insertMany():向指定集合中插入多条文档数据。
test> var res = db.test.insertMany([{'key1': 'value1'}, {'key2': 'value2'}])

test> res
{
acknowledged: true,
insertedIds: {
 '0': ObjectId("64c888fa5a6d80b51405549e"),
 '1': ObjectId("64c888fa5a6d80b51405549f")
}
}
运行结果
运行结果

MongoDB更新文档

MongoDB使用update方法和save方法更新集合中的文档。

使用update方法更新文档的语法如下。

db.collection.update(
	<query>,
	<update>,
	{
		upsert: <boolean>,
		multi: <boolean>,
		writeConcern: <document>
	}
)

参数说明如下。

  • query:update的查询条件。
  • update:更新操作符。
  • upsert:可选参数,在不存在update记录的情况下,为True则插入objNew,默认为False。
  • multi:可选参数,默认是False,只更新找到的第一条记录。如果为True,找到的记录全部更新。
  • writeConcern:可选参数,表示抛出异常的级别。

update方法已经弃用,推荐使用下方的updateOneupdateMany

updateOne方法和update方法语法类似,updateMany方法和insertMany方法比较类似。

首先插入数据。

test> use test
already on db test
test> db.test_collection.insertMany([
... {"name":"abc","age":"25","status":"zxc"},
... {"name":"dec","age":"19","status":"qwe"},
... {"name":"asd","age":"30","status":"nmn"},
... ])
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId("64c90ad84cc5b7546c70efdb"),
    '1': ObjectId("64c90ad84cc5b7546c70efdc"),
    '2': ObjectId("64c90ad84cc5b7546c70efdd")
  }
}
运行结果
运行结果

更新一条数据。

test> db.test_collection.updateOne({'name': 'abc'}, {$set: {'age': '28'}})
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
test> db.test_collection.find()
[
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdb"),
    name: 'abc',
    age: '28',
    status: 'zxc'
  },
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdc"),
    name: 'dec',
    age: '19',
    status: 'qwe'
  },
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdd"),
    name: 'asd',
    age: '30',
    status: 'nmn'
  }
]
运行结果
运行结果

更新多个文档。

test> db.test_collection.updateMany({'age': {$gt: '10'}}, {$set: {'status': 'xyz'}})
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 3,
  modifiedCount: 3,
  upsertedCount: 0
}
test> db.test_collection.find()
[
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdb"),
    name: 'abc',
    age: '28',
    status: 'xyz'
  },
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdc"),
    name: 'dec',
    age: '19',
    status: 'xyz'
  },
  {
    _id: ObjectId("64c90ad84cc5b7546c70efdd"),
    name: 'asd',
    age: '30',
    status: 'xyz'
  }
]
运行结果
运行结果

参数writeConcern可选的值如下。

  • WriteConcern.NONE:没有异常抛出
  • WriteConcern.NORMAL:仅抛出网络错误异常,没有服务器错误异常
  • WriteConcern.SAFE:抛出网络错误异常、服务器错误异常;并等待服务器完成写操作。
  • WriteConcern.MAJORITY: 抛出网络错误异常、服务器错误异常;并等待一个主服务器完成写操作。
  • WriteConcern.FSYNC_SAFE: 抛出网络错误异常、服务器错误异常;写操作等待服务器将数据刷新到磁盘。
  • WriteConcern.JOURNAL_SAFE:抛出网络错误异常、服务器错误异常;写操作等待服务器提交到磁盘的日志文件。
  • WriteConcern.REPLICAS_SAFE:抛出网络错误异常、服务器错误异常;等待至少2台服务器完成写操作。

移除集合中的键值对,使用的 $unset 操作符:

语法:

{ $unset: { <field1>: "", ... } }

如果指定的字段不存在则操作不做任何处理。

db.col.updateOne({"_id":"56064f89ade2f21f36b03136"}, {$set:{ "test2" : "OK"}})
db.col.find()

db.col.updateOne({"_id":"56064f89ade2f21f36b03136"}, {$unset:{ "test2" : "OK"}})
db.col.find()

数据存在则更新:

db.collection.updateOne({'title':'MongoDB 教程'}, {$set: {'title':'MongoDB'}})

数据存在时不进行操作:

db.collection.updateOne({'title':'MongoDB 教程'}, {$setOnInsert: {'title':'MongoDB'}})

以上可扩展为多个字段(查询,更新),如果数据不存在需要插入,设置 upsert:true 即可:

db.collection.updateOne({'title':'MongoDB 教程'}, {$set: {'title':'MongoDB'}},{upsert:true})

MongoDB删除文档

实际上最初remove是用于删除文档的方法,但是此方法目前已经过时。

deleteOne方法用于删除一行文档,deleteMany用于删除多行文档。

db.inventory.deleteMany({})

删除 status 等于 A 的全部文档:

db.inventory.deleteMany({ status : "A" })

删除 status 等于 D 的一个文档:

db.inventory.deleteOne( { status: "D" } )

MongoDB文档的查询

MongoDB 查询文档使用 find() 方法。

find() 方法以非结构化的方式来显示所有文档。

语法

MongoDB 查询数据的语法格式如下:

db.collection.find(query, projection)
  • query :可选,使用查询操作符指定查询条件。
  • projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。

如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

db.col.find().pretty()

pretty() 方法以格式化的方式来显示所有文档。

示例:

以下实例我们查询了集合 col 中的数据:

test> db.test.find()
[
  {
    _id: ObjectId("64ce5c677d3e3193854c6c98"),
    name: 'qi1',
    value: 'qi1'
  },
  {
    _id: ObjectId("64ce5c677d3e3193854c6c99"),
    name: 'qi2',
    value: 'qi2'
  }
]
test> db.test.find().pretty()
[
  {
    _id: ObjectId("64ce5c677d3e3193854c6c98"),
    name: 'qi1',
    value: 'qi1'
  },
  {
    _id: ObjectId("64ce5c677d3e3193854c6c99"),
    name: 'qi2',
    value: 'qi2'
  }
]
运行结果
运行结果

除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。

MongoDB 与 RDBMS Where 语句比较

如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件语句查询:

操作格式范例RDBMS中的类似语句
等于{<key>:<value>}db.col.find({"by":"菜鸟教程"}).pretty()where by = '菜鸟教程'
小于{<key>:{$lt:<value>}}db.col.find({"likes":{$lt:50}}).pretty()where likes < 50
小于或等于{<key>:{$lte:<value>}}db.col.find({"likes":{$lte:50}}).pretty()where likes <= 50
大于{<key>:{$gt:<value>}}db.col.find({"likes":{$gt:50}}).pretty()where likes > 50
大于或等于{<key>:{$gte:<value>}}db.col.find({"likes":{$gte:50}}).pretty()where likes >= 50
不等于{<key>:{$ne:<value>}}db.col.find({"likes":{$ne:50}}).pretty()where likes != 50

MongoDB AND 条件

MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。

语法格式如下:

db.col.find({key1:value1, key2:value2}).pretty()

实例

test> db.stu.find()
[
  {
    _id: ObjectId("64ce5f7e7d3e3193854c6ca3"),
    name: 'qi1',
    age: 38,
    score: 80
  },
  {
    _id: ObjectId("64ce5f867d3e3193854c6ca4"),
    name: 'qi2',
    age: 28,
    score: 90
  },
  {
    _id: ObjectId("64ce5f8d7d3e3193854c6ca5"),
    name: 'qi3',
    age: 18,
    score: 100
  }
]
test> db.stu.find({'age': {$lt: 30}})
[
  {
    _id: ObjectId("64ce5f867d3e3193854c6ca4"),
    name: 'qi2',
    age: 28,
    score: 90
  },
  {
    _id: ObjectId("64ce5f8d7d3e3193854c6ca5"),
    name: 'qi3',
    age: 18,
    score: 100
  }
]
test> db.stu.find({'age': {$lt: 30}, 'score': {$gt: 95}})
[
  {
    _id: ObjectId("64ce5f8d7d3e3193854c6ca5"),
    name: 'qi3',
    age: 18,
    score: 100
  }
]
运行结果
运行结果

MongoDB OR 条件

MongoDB OR 条件语句使用了关键字 $or,语法格式如下:

db.col.find(
   {
      $or: [
         {key1: value1}, {key2:value2}
      ]
   }
).pretty()

实例

以下实例中,我们演示了查询键 by 值为 菜鸟教程 或键 title 值为 MongoDB 教程 的文档。

db.col.find({$or:[{"by":"菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
{
        "_id" : ObjectId("56063f17ade2f21f36b03133"),
        "title" : "MongoDB 教程",
        "description" : "MongoDB 是一个 Nosql 数据库",
        "by" : "菜鸟教程",
        "url" : "http://www.runoob.com",
        "tags" : [
                "mongodb",
                "database",
                "NoSQL"
        ],
        "likes" : 100
}
>

AND 和 OR 联合使用

以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: 'where likes>50 AND (by = '菜鸟教程' OR title = 'MongoDB 教程')'

db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty()
{
        "_id" : ObjectId("56063f17ade2f21f36b03133"),
        "title" : "MongoDB 教程",
        "description" : "MongoDB 是一个 Nosql 数据库",
        "by" : "菜鸟教程",
        "url" : "http://www.runoob.com",
        "tags" : [
                "mongodb",
                "database",
                "NoSQL"
        ],
        "likes" : 100
}

MongoDB $type运算符

MongoDB支持的数据类型如下表。

类型数字备注
Double1
String2
Object3
Array4
Binary data5
Undefined6已废弃。
Object id7
Boolean8
Date9
Null10
Regular Expression11
JavaScript13
Symbol14
JavaScript (with scope)15
32-bit integer16
Timestamp17
64-bit integer18
Min key255Query with -1.
Max key127

我们先向下表中插入如下数据。

test> db.col.insertOne({
...     title: 'PHP 教程',
...     description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
...     by: '菜鸟教程',
...     url: 'http://www.runoob.com',
...     tags: ['php'],
...     likes: 200
... })
{
  acknowledged: true,
  insertedId: ObjectId("64ce61ef7d3e3193854c6ca6")
}
test> db.col.insertOne({title: 'Java 教程',
...     description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
...     by: '菜鸟教程',
...     url: 'http://www.runoob.com',
...     tags: ['java'],
...     likes: 150
... })
{
  acknowledged: true,
  insertedId: ObjectId("64ce61f67d3e3193854c6ca7")
}
test> db.col.insertOne({title: 'MongoDB 教程',
...     description: 'MongoDB 是一个 Nosql 数据库',
...     by: '菜鸟教程',
...     url: 'http://www.runoob.com',
...     tags: ['mongodb'],
...     likes: 100
... })
{
  acknowledged: true,
  insertedId: ObjectId("64ce61fd7d3e3193854c6ca8")
}

然后使用find查看数据。

test> db.col.find()
[
  {
    _id: ObjectId("64ce61ef7d3e3193854c6ca6"),
    title: 'PHP 教程',
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'php' ],
    likes: 200
  },
  {
    _id: ObjectId("64ce61f67d3e3193854c6ca7"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  },
  {
    _id: ObjectId("64ce61fd7d3e3193854c6ca8"),
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'mongodb' ],
    likes: 100
  }
]

现在我们就可以在查询时指定$type运算符了。

test> db.col.find({"title" : {$type : 'string'}})
[
  {
    _id: ObjectId("64ce61ef7d3e3193854c6ca6"),
    title: 'PHP 教程',
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'php' ],
    likes: 200
  },
  {
    _id: ObjectId("64ce61f67d3e3193854c6ca7"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  },
  {
    _id: ObjectId("64ce61fd7d3e3193854c6ca8"),
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'mongodb' ],
    likes: 100
  }
]

由于上方插入数据时title键的值都为字符串类型,所以三条结果都返回了。

MongoDB Limit与Skip方法

MongoDB Limit() 方法

如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。

语法

limit()方法基本语法如下所示:

db.COLLECTION_NAME.find().limit(NUMBER)

实例

在上方我们插入了三条记录,使用find方法会将三条记录全部返回,这里我们使用limit方法仅返回两条记录。

test> db.col.find().limit(2)
[
  {
    _id: ObjectId("64ce61ef7d3e3193854c6ca6"),
    title: 'PHP 教程',
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'php' ],
    likes: 200
  },
  {
    _id: ObjectId("64ce61f67d3e3193854c6ca7"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  }
]
运行结果
运行结果

MongoDB Skip() 方法

我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

语法

skip() 方法脚本语法格式如下:

db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

实例

test> db.col.find().skip(1)
[
  {
    _id: ObjectId("64ce61f67d3e3193854c6ca7"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  },
  {
    _id: ObjectId("64ce61fd7d3e3193854c6ca8"),
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'mongodb' ],
    likes: 100
  }
]
运行结果
运行结果

MongoDB排序

sort()方法

在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

db.COLLECTION_NAME.find().sort({KEY:1})

现在插入如下数据。

db.test.insertOne({ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 })
db.test.insertOne({ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 })
db.test.insertOne({ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 })

按照likes降序排列:

test> db.test.find().sort({'likes':-1})
[
  {
    _id: ObjectId("56066542ade2f21f36b0313a"),
    title: 'PHP 教程',
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'php' ],
    likes: 200
  },
  {
    _id: ObjectId("56066549ade2f21f36b0313b"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  },
  {
    _id: ObjectId("5606654fade2f21f36b0313c"),
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'mongodb' ],
    likes: 100
  }
]
运行结果
运行结果

按照likes升序排列:

test> db.test.find().sort({'like':1})
[
  {
    _id: ObjectId("56066542ade2f21f36b0313a"),
    title: 'PHP 教程',
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'php' ],
    likes: 200
  },
  {
    _id: ObjectId("56066549ade2f21f36b0313b"),
    title: 'Java 教程',
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'java' ],
    likes: 150
  },
  {
    _id: ObjectId("5606654fade2f21f36b0313c"),
    title: 'MongoDB 教程',
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: [ 'mongodb' ],
    likes: 100
  }
]
运行结果
运行结果

索引

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。

这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

createIndex()创建索引

createIndex()的语法如下:

db.collection.createIndex(keys, options)

参数解释如下:

  • key:要创建的索引字段,1为按照升序创建索引,-1为按照降序创建索引。
db.col.createIndex({"title":1})

上方的表达式代表按照升序为title字段创建索引。

createIndex()还可以为多列创建索引,只需要指定多个keys参数即可,类似于关系型数据库中的复合索引。

db.col.createIndex({"title":1,"description":-1})
  • option:指定一些附加参数,如下表所示。
参数类型描述
backgroundBoolean建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false
uniqueBoolean建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
namestring索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
dropDupsBoolean**3.0+版本已废弃。**在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.
sparseBoolean对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSecondsinteger指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
vindex version索引的版本号。默认的索引版本取决于mongodb创建索引时运行的版本。
weightsdocument索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_languagestring对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_overridestring对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

如在后台创建索引:

db.values.createIndex({open: 1, close: 1}, {background: true})

1、查看集合索引

db.col.getIndexes()

2、查看集合索引大小

db.col.totalIndexSize()

3、删除集合所有索引

db.col.dropIndexes()

4、删除集合指定索引

db.col.dropIndex("索引名称")

利用 TTL 集合对存储的数据进行失效时间设置:经过指定的时间段后或在指定的时间点过期,MongoDB 独立线程去清除数据。类似于设置定时自动删除任务,可以清除历史记录或日志等前提条件,设置 Index 的关键字段为日期类型 new Date()。

例如数据记录中 createDate 为日期类型时:

  • 设置时间180秒后自动清除。
  • 设置在创建记录后,180 秒左右删除。
db.col.createIndex({"createDate": 1},{expireAfterSeconds: 180})

由记录中设定日期点清除。

设置 A 记录在 2019 年 1 月 22 日晚上 11 点左右删除,A 记录中需添加 "ClearUpDate": new Date('Jan 22, 2019 23:00:00'),且 Index中expireAfterSeconds 设值为 0。

db.col.createIndex({"ClearUpDate": 1},{expireAfterSeconds: 0})

其他注意事项:

  • 索引关键字段必须是 Date 类型。
  • 非立即执行:扫描 Document 过期数据并删除是独立线程执行,默认 60s 扫描一次,删除也不一定是立即删除成功。
  • 单字段索引,混合索引不支持。

聚合

MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。

有点类似 SQL 语句中的 count(*)

aggregate() 方法

MongoDB中聚合的方法使用aggregate()。

aggregate() 方法的基本语法格式如下所示:

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

如在集合中插入如下数据:

db.demo.insertMany([{
   title: 'MongoDB Overview', 
   description: 'MongoDB is no sql database',
   by_user: 'runoob.com',
   url: 'http://www.runoob.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 100
},
{
   title: 'NoSQL Overview', 
   description: 'No sql database is very fast',
   by_user: 'runoob.com',
   url: 'http://www.runoob.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 10
},
{
   title: 'Neo4j Overview', 
   description: 'Neo4j is no sql database',
   by_user: 'Neo4j',
   url: 'http://www.neo4j.com',
   tags: ['neo4j', 'database', 'NoSQL'],
   likes: 750
}])

现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:

demo> db.demo.aggregate([{$group: {_id: '$by_user', num_tutorial: {$sum:1}}}])
[
  { _id: 'runoob.com', num_tutorial: 2 },
  { _id: 'Neo4j', num_tutorial: 1 }
]

以上实例类似sql语句:

 select by_user, count(*) from demo group by by_user

在上面的例子中,我们通过字段 by_user 字段对数据进行分组,并计算 by_user 字段相同值的总和。

下表展示了一些聚合的表达式:

表达式描述实例
$sum计算总和。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
$avg计算平均值db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
$min获取集合中所有文档对应值得最小值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
$max获取集合中所有文档对应值得最大值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
$push将值加入一个数组中,不会判断是否有重复的值。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
$addToSet将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
$first根据资源文档的排序获取第一个文档数据。db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
$last根据资源文档的排序获取最后一个文档数据db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

管道的概念

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

这里我们介绍一下聚合框架中常用的几个操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

管道操作符实例

1、$project实例

db.article.aggregate(
    { $project : {
        title : 1 ,
        author : 1 ,
    }}
 );

这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id的话可以这样:

db.article.aggregate(
    { $project : {
        _id : 0 ,
        title : 1 ,
        author : 1
    }});

2.$match实例

db.articles.aggregate( [
                        { $match : { score : { $gt : 70, $lte : 90 } } },
                        { $group: { _id: null, count: { $sum: 1 } } }
                       ] );

$match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。

3.$skip实例

db.article.aggregate(
    { $skip : 5 });

经过$skip管道操作符处理后,前五个文档被"过滤"掉。

Java连接MongoDB

示例

下载mongodb.jar、配置等步骤略。

mongodb.jar下载地址:https://repo1.maven.org/maven2/org/mongodb/mongo-java-driver/。open in new window

使用Java连接MongoDB与使用Java连接MySQL比较类似,下面直接给出代码。

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("test");
            System.out.println("Connect to database successfully");
            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

运行结果如下。

运行结果
运行结果

若MongoDB需要用户名和密码验证,则可以使用下面的代码。

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

public class Main {
    public static void main(String[] args) {
        String username = "your_username";
        String database = "your_database";
        String password = "your_password";
        String hostname = "mongodb_host";
        int port = 27017;

        // 创建MongoCredential对象
        MongoCredential credential = MongoCredential.createCredential(username, database, password.toCharArray());

        // 创建MongoClientSettings对象,并配置连接参数
        ConnectionString connectionString = new ConnectionString("mongodb://" + hostname + ":" + port);
        MongoClientSettings settings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .credential(credential)
                .build();

        // 创建MongoClient对象
        com.mongodb.client.MongoClient mongoClient = MongoClients.create(settings);

        // 连接到数据库和集合
        MongoDatabase databaseObj = mongoClient.getDatabase(database);
        MongoCollection<Document> collection = databaseObj.getCollection("your_collection_name");

        // 执行CRUD操作
        Document document = new Document("key", "value");
        collection.insertOne(document);

        // 关闭MongoDB客户端连接
        mongoClient.close();
    }
}

此代码未运行,仅作参考。

创建集合

MongoDatabase类提供了createCollection()方法创建集合。

以下代码示例中MongoDB数据库存在日志和输出语句混合的情况,这里不放出运行结果了,但是代码都是能够运行的。

import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");
            mongoDatabase.createCollection("test");
            System.out.println("集合创建成功");

            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

随后访问MongoDB,可以看到集合创建成功了。

运行结果
运行结果

获取集合

MongoDatabase类提供了getDatabase()来获取集合。

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");

            MongoCollection<Document> collection = mongoDatabase.getCollection("test");
            System.out.println("集合 test 选择成功");

            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

插入文档

MongoCollection提供了insertOne()和insertMany()来插入文档。

import java.util.ArrayList;
import java.util.List;

import org.bson.Document;

import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");

            MongoCollection<Document> collection = mongoDatabase.getCollection("test");
            System.out.println("集合 test 选择成功");
            //插入文档
            /*
              1. 创建文档 org.bson.Document 参数为key-value的格式
              2. 创建文档集合List<Document>
              3. 将文档集合插入数据库集合中 mongoCollection.insertMany(List<Document>) 插入单个文档可以用 mongoCollection.insertOne(Document)
              */
            Document document = new Document("title", "MongoDB").
                    append("description", "database").
                    append("likes", 100).
                    append("by", "Fly");
            List<Document> documents = new ArrayList<>();
            documents.add(document);
            collection.insertMany(documents);
            System.out.println("文档插入成功");

            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

运行后在MongoDB运行查询可查看到插入后的数据。

运行结果
运行结果

检索所有文档

MongoCollection类提供了一个find()方法用于检索文档,该方法返回一个游标对象,因此需要遍历游标对象。

在举例之前只有一条记录,现插入两条记录,插入后的数据如下。

运行结果
运行结果
import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");

            MongoCollection<Document> collection = mongoDatabase.getCollection("test");
            System.out.println("集合 test 选择成功");

            //检索所有文档
            /*
             * 1. 获取迭代器FindIterable<Document>
             * 2. 获取游标MongoCursor<Document>
             * 3. 通过游标遍历检索出的文档集合
             * */
            FindIterable<Document> findIterable = collection.find();
            MongoCursor<Document> mongoCursor = findIterable.iterator();
            while (mongoCursor.hasNext()) {
                System.out.println(mongoCursor.next());
            }
            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

除日志外的输出结果如下。

运行结果
运行结果

更新文档

MongoCollection提供了一个updateOne()和updateMany()方法用于更新文档。

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");

            MongoCollection<Document> collection = mongoDatabase.getCollection("test");
            System.out.println("集合 test 选择成功");

            //更新文档   将文档中likes=100的文档修改为likes=200
            collection.updateMany(Filters.eq("likes", 100), new Document("$set", new Document("likes", 200)));
            //检索查看结果
            FindIterable<Document> findIterable = collection.find();
            MongoCursor<Document> mongoCursor = findIterable.iterator();
            while (mongoCursor.hasNext()) {
                System.out.println(mongoCursor.next());
            }
            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

更新文档后,在mongosh运行查询可以看到被更改的数据。

运行结果
运行结果

删除文档

可以先使用DBCollection类提供的findOne()方法先获取第一个文档,然后使用remove()方法将其删除即可。

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;

public class MongoDBJDBC{
public static void main( String args[] ){
try{
// 连接到 mongodb 服务
MongoClient mongoClient = new MongoClient( "localhost" , 27017 );

import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;

public class Main {
    public static void main(String[] args) {
        try {
            // 连接到 mongodb 服务
            MongoClient mongoClient = new MongoClient("localhost", 27017);

            // 连接到数据库
            MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
            System.out.println("Connect to database successfully");

            MongoCollection<Document> collection = mongoDatabase.getCollection("test");
            System.out.println("集合 test 选择成功");

            //删除符合条件的第一个文档
            collection.deleteOne(Filters.eq("likes", 200));
            //删除所有符合条件的文档
//            collection.deleteMany(Filters.eq("likes", 200));
            //检索查看结果
            FindIterable<Document> findIterable = collection.find();
            MongoCursor<Document> mongoCursor = findIterable.iterator();
            while (mongoCursor.hasNext()) {
                System.out.println(mongoCursor.next());
            }

            mongoCursor.close();
            mongoClient.close();
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
        }
    }
}

随后在mongosh运行查询可以看到变动后的数据。

运行结果
运行结果

Python连接MongoDB

一般使用pymongo连接MongoDB数据库。在venv环境内使用命令pip install pymongo可以安装此模块。

连接数据库

pymongo.MongoClient方法用于创建连接对象,该方法支持MongoDB格式的URL字符串作为参数,也支持指定主机和端口。

client = pymongo.MongoClient(host='localhost', proxy=27019)
client = pymongo.MongoClient('mongodb://localhost:27019/')

标准的URI格式如下。

mongodb://[username:password@]host1[:post1][,host2[:post2]]...[,hostN...][/database][?options]

部分参数说明如下。

  • options:连接选项,若前面不使用/database,则前面要带上/

常见的连接URI格式如下:

mongodb://localhost
mongodb://admin:123456@localhost/db1

获得client对象后可以通过list_database_names方法获取数据库列表,通过client[dbname]选择或创建一个数据库。

from pymongo import MongoClient

client = MongoClient()
print(client.list_database_names())
db = client['demo']
运行结果
运行结果

选取数据库之后,还需要进行集合的操作,即操作文档集合。操作文档集合也是使用db[collection_name]方式。使用db.list_collection_names()可以获取db数据库中的所有文档集合名。

db.list_collection_names()
col = db['test']
col.insert_one({'user': 'name'})
print(db.list_collection_names())
运行结果
运行结果

下面的例子将使用一个新数据库db,然后使用集合collection。

from pymongo import MongoClient

client = MongoClient()
db = client['db']
col = db['collection']

插入文档

插入单个文档时使用insert_one()方法,插入多个文档时使用insert_Many()方法。前者接受一个字典参数代表要插入的文档,该方法返回一个InsertOneResult对象,该对象包含了inserted_id属性;后者接受一个字典列表作为参数,返回InsertOneResult对象列表。

若插入文档时没有使用_id,MongoDB会自动为每一个文档生成独一无二的id值。

运行前,db数据库内的collection集合没有文档。

运行结果
运行结果
# 插入一条数据
col.insert_one({'key1': 'value1'})

# 插入多条数据
col.insert_many([{'key2': 'value2'}, {'key3': 'value3'}])

运行结果如下。

运行结果
运行结果

随后在命令行运行查询,可以看到插入后的数据。

运行结果
运行结果

查询文档

pymongo提供了类似MongoDB客户端的find()和findOne()方法对数据进行查询,在pymongo模块中对应的方法为find、find_one。这两个方法的语法和MongoDB客户端类似。

>>> result = col.find()
>>> list(result)
[{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 18}, {'_id': ObjectId('64da42c9deb5a77acd00541f'), 'name': 'Peter', 'age': 19}, {'_id': ObjectId(
'64da42c9deb5a77acd005420'), 'name': 'Bill', 'age': 20}, {'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, {'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}]

>>> col.find_one()
{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 18}

>>> result = col.find({'age': {'$gt': 20}})
>>> list(result) 
[{'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, {'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}]

>>> result = col.find({'name': 'Jack'})
>>> list(result)
[{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 18}]

pymongo的find、find_one、limit、skip方法和MongoDB中对应的语法一致,也支持使用比较运算符、逻辑运算符、正则表达式、聚合操作。

修改文档

使用update_one()update_many()方法可以修改文档中的记录。两个方法的参数一样,第一个参数为查询的条件,第二个参数为要修改的字段。

现在从MongoDB客户端查询到的数据如下:

运行结果
运行结果
>>> result = col.update_one({'name': 'Jack'},{'$set': {'age': 25}}) 
>>> result = col.find()
>>> list(result)
[{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 25}, 
{'_id': ObjectId('64da42c9deb5a77acd00541f'), 'name': 'Peter', 'age': 19}, 
{'_id': ObjectId('64da42c9deb5a77acd005420'), 'name': 'Bill', 'age': 20}, 
{'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, 
{'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}]

可以看到Jack的年龄已经改变。从MongoDB的客户端查询结果也可以看出改变成功了。

运行结果
运行结果

查询结果排序

sort方法可以指定查询的结果是升序还是降序排列,第一个参数为要排序的字段,第二个参数为排序规则,1为升序,-1为降序。

>>> result = col.find().sort('age', 1)   
>>> list(result)
[{'_id': ObjectId('64da42c9deb5a77acd00541f'), 'name': 'Peter', 'age': 19}, 
 {'_id': ObjectId('64da42c9deb5a77acd005420'), 'name': 'Bill', 'age': 20}, 
 {'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, 
 {'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}, 
 {'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 25}]

>>> result = col.find().sort('age', -1) 
>>> list(result)                        
[{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 25}, 
 {'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}, 
 {'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, 
 {'_id': ObjectId('64da42c9deb5a77acd005420'), 'name': 'Bill', 'age': 20}, 
 {'_id': ObjectId('64da42c9deb5a77acd00541f'), 'name': 'Peter', 'age': 19}]

删除操作

常见的操作有删除文档,删除集合。使用delete_onedelete_many方法可以删除文档,它们的第一个参数都为查询条件,使用drop方法删除一个集合,若删除成功会返回True。

>>> result = col.delete_many({'age': {'$lt': 21}})
>>> result = col.find()
>>> list(result)
[{'_id': ObjectId('64da42c9deb5a77acd00541e'), 'name': 'Jack', 'age': 25}, 
 {'_id': ObjectId('64da42c9deb5a77acd005421'), 'name': 'Sun', 'age': 21}, 
 {'_id': ObjectId('64da42c9deb5a77acd005422'), 'name': 'Lin', 'age': 22}]

>>> col.drop()

最后在MongoDB客户端运行查询也可以看到collection集合被成功删除了。

上次编辑于:
贡献者: QI