MongoDB是一个基于分布式文件存储的数据库,由C++编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB将数据存储为一个文档,数据结构由键值对组成,MongoDB文档类似于JSON对象,字段值可以包含其他文档,数组及文档数组。
MongoDB服务端可运行在Linux、Windows或macOS X平台,支持32位和64位应用,默认端口为27017。
安装
Windows
进入MongoDB官网下载预编译二进制包(下载地址:https://www.mongodb.com/download-center/community),打开之后选择Version后,点击Download下载即可。
msi安装向导
点击下载好的mongodb-windows-x86_64-6.0.5-signed.msi,安装类型需要选择Custom;

修改安装目录为:D:\usr\local\mongodb;

服务配置保持默认值;

需要将Install MongoDB Compass的勾去掉,否则安装过程中会下载Install MongoDB Compass并安装,比较耗费时间。Install MongoDB Compass是一个图形界面管理工具,可以使用其他GUI管理工具,如:Navicat for MongoDB。

之后一直Next直到安装完成。
安装完成后,浏览器访问:http://localhost:27017/,如果显示:It looks like you are trying to access MongoDB over HTTP on the native driver port.说明MongoDB已经安装成功了。
zip手动安装
下载地址:https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-6.0.10.zip
- 解压mongodb-windows-x86_64-6.0.10.zip文件到:
D:\usr\local\mongodb; - 切换到
D:\usr\local\mongodb目录,创建data和log目录,分别用于存储数据库和日志文件; 切换到D:\usr\local\mongodb目录,创建配置文件mongod.cfg,内容参考:
storage:
dbPath: D:\usr\local\mongodb\data
systemLog:
destination: file
logAppend: true
path: D:\usr\local\mongodb\log\mongod.log
net:
port: 27017
bindIp: 127.0.0.1
注册MongoDB服务
C:\Users\Administrator> D:\usr\local\mongodb\bin\mongod --config "D:\usr\local\mongodb\mongod.cfg" --install --serviceName "MongoDB"
启动MongoDB服务
C:\Users\Administrator> net start mongodb
卸载MongoDB服务
C:\Users\Administrator> D:\usr\local\mongodb\bin\mongod --remove
Linux
5.0.9
[root@ecs src]# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.9.tgz
[root@ecs src]# tar -zxvf mongodb-linux-x86_64-rhel70-5.0.9.tgz -C /usr/local
[root@ecs src]# mv /usr/local/mongodb-linux-x86_64-rhel70-5.0.9 /usr/local/mongodb
[root@ecs src]# mkdir /usr/local/mongodb/{data,log}
[root@ecs src]# cat > /etc/mongodb.cnf <<EOF
dbpath=/usr/local/mongodb/data
logpath=/usr/local/mongodb/log/mongodb.log
port=27017
fork=true
journal=false
bind_ip=0.0.0.0
auth=true
EOF
[root@ecs src]# echo "export PATH=\$PATH:/usr/local/mongodb/bin/" >> /etc/profile
[root@ecs src]# source /etc/profile
[root@ecs src]# mongod -f /etc/mongodb.cnf
about to fork child process, waiting until server is ready for connections.
forked process: 20927
child process started successfully, parent exiting
fork=true:后台运行auth=true:开启授权登录模式
重启
> use admin
> db.shutdownServer()
server should be down...
> exit
[root@ecs ~]# mongod --config /etc/mongodb.cnf
7.0.11
[root@ecs src]# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-7.0.11.tgz
[root@ecs src]# tar -zxvf mongodb-linux-x86_64-rhel70-7.0.11.tgz
[root@ecs src]# mv mongodb-linux-x86_64-rhel70-7.0.11 /usr/local/mongodb
[root@ecs src]# mkdir /usr/local/mongodb/{data,log}
[root@ecs src]# cat > /etc/mongodb.conf <<EOF
storage:
dbPath: /usr/local/mongodb/data
systemLog:
destination: file
logAppend: true
path: /usr/local/mongodb/log/mongod.log
net:
port: 27017
bindIp: 0.0.0.0
processManagement:
fork: true
pidFilePath: /var/run/mongod.pid
timeZoneInfo: /usr/share/zoneinfo
security:
authorization: enabled
EOF
[root@ecs src]# echo "export PATH=\$PATH:/usr/local/mongodb/bin/" >> /etc/profile
[root@ecs src]# source /etc/profile
[root@ecs src]# mongod -f /etc/mongodb.conf
[root@ecs src]# ps -aux | grep 'mongod' | grep -v 'grep'
root 26335 0.8 1.3 2699260 105684 ? Sl 03:11 0:04 mongod -f /etc/mongodb.conf
fork: true:以守护程序的方式启用(后台运行)bindIp: 0.0.0.0:允许外网访问authorization: enabled:开启授权模式
Docker
创建容器;
7.0.11
[root@ecs ~]# docker run --name mongo \
--restart=always \
-p 27017:27017 \
-v /data/mongo/data:/data/db \
-v /data/mongo/log:/data/log \
--privileged=true \
-d registry.cn-shanghai.aliyuncs.com/maisi/mongo:7.0.11 --auth
设置管理员账号和密码:
[root@ecs ~]# docker run --name mongo \
--restart=always \
-p 27017:27017 \
-v /data/mongo/data:/data/db \
-v /data/mongo/log:/data/log \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=ay2ikfyYonmKvFkS \
--privileged=true \
-d registry.cn-shanghai.aliyuncs.com/maisi/mongo:7.0.11
4.2.8
[root@ecs ~]# docker run --name mongo \
--restart=always \
-p 27017:27017 \
-v /data/mongo/data:/data/db \
-v /data/mongo/log:/data/log \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=ay2ikfyYonmKvFkS \
--privileged=true \
-d registry.cn-shanghai.aliyuncs.com/maisi/mongo:4.2.8
--auth要求必须验证密码才能进行基于角色的访问控制,没有密码虽然可以登录shell,但是无法执行实质性的数据操作;- 即使没有添加
--auth,只要设置了MONGO_INITDB_ROOT_USERNAME和MONGO_INITDB_ROOT_PASSWORD环境变量,则自动追加--auth; - 即使设置了
--auth,由于MongoDB有本地例外(Localhost Exception)机制,即数据库中没有创建任何超级用户时,为了防止锁死,仍然允许localhost或127.0.0.1发起的连接拥有最高权限。添加了超级用户后,本地例外机制失效。
操作MongoDB
[root@ecs ~]# docker exec -it mongo /bin/bash
进入mongo shell
root@38857e5751d8:/# mongosh
Current Mongosh Log ID: 6a6225fea56d2a74a7a26a12
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.6
Using MongoDB: 7.0.11
Using Mongosh: 2.2.6
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.
test>
登录admin数据库
[root@ecs ~]# docker exec -it mongo mongosh admin
Current Mongosh Log ID: 6a6226363f9e10a413a26a12
Connecting to: mongodb://127.0.0.1:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.6
Using MongoDB: 7.0.11
Using Mongosh: 2.2.6
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
admin>
配置外网访问
进入mongodb容器
[root@ecs ~]# docker exec -it mongo /bin/bash
容器内修改配置文件
root@38857e5751d8:/# cp /etc/mongod.conf.orig /etc/mongod.conf
root@38857e5751d8:/# sed -i 's/bindIp: 127.0.0.1/bindIp: 0.0.0.0/g' /etc/mongod.conf
退出容器并重启mongodb容器
root@38857e5751d8:/# exit
[root@ecs ~]# docker restart mongo
客户端操作
安装Navicat for MongoDB,创建数据库连接:
如果MongoDB设置了以授权方式登录,验证类型需要选择Password,验证数据库选择admin,输入用户名和密码后,可以点击测试连接测试一下数据库是否连接成功。

点击确定即可创建数据库连接了。
Navicat for MongoDB激活方式:
打开注册机程序,Patch勾选Backup、Host,选择Navicat v 15。License选择Enterprise,Product选择MongoDB,Language选择Simplified Chinese。

点击Patch,出现以下提示表示navicat.exe破解成功,此时还没有许可证,接着往下操作;

Keygen / Offline Activation,点击Generate生成序列号;

重新打开Navicat for MongoDB,在试用提醒对话框中选择“注册”;

输入第2步生成的序列号,点击“激活”;

选择“手动激活”,弹出“手动激活”对话框,复制请求码;


切换到Navicat Products - Patch/Keygen,将复制的请求码粘贴到Request Code输入框中,点击Activation Code下面的Generate生成激活码;

最后再将激活码回填到Navicat for MongoDB的激活窗口中,点击“激活”;

出现永久许可证界面,表明已经激活成功了。

命令行操作
数据库
进入mongo shell
MongoDB 6之前版本:
[root@ecs ~]# mongo
MongoDB shell version v5.0.9
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("dc9cb95a-f071-49de-bd6f-0dacae8e3b00") }
MongoDB server version: 5.0.9
================
Warning: the "mongo" shell has been superseded by "mongosh",
which delivers improved usability and compatibility.The "mongo" shell has been deprecated and will be removed in
an upcoming release.
For installation instructions, see
https://docs.mongodb.com/mongodb-shell/install/
================
>
MongoDB自6.0开始不再默认安装shell工具,需要额外安装mongosh,下载地址:https://www.mongodb.com/try/download/shell。
[root@ecs ~]# mongosh
连接远程数据库
[root@ecs ~]# mongosh -u root -p root --port 27017 --host 192.168.1.215
获取mongdb版本
admin> db.version()
7.0.11
admin> db.system.version.find()
[
{ _id: 'featureCompatibilityVersion', version: '7.0' },
{ _id: 'authSchema', currentVersion: 5 }
]
admin>
授权登录
方法1
admin> db.auth('admin', 'ay2ikfyYonmKvFkS')
{ ok: 1 }
admin>
方法2
[root@ecs ~]# mongosh admin -u admin -p ay2ikfyYonmKvFkS
Current Mongosh Log ID: 6a6237969b309148aaa26a12
Connecting to: mongodb://<credentials>@127.0.0.1:27017/admin?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.2.6
Using MongoDB: 7.0.11
Using Mongosh: 2.2.6
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
admin>
方法3
[root@ecs ~]# mongosh mongodb://admin:'ay2ikfyYonmKvFkS'@127.0.0.1:27017
[root@ecs ~]# mongosh mongodb://admin:'ay2ikfyYonmKvFkS'@127.0.0.1:27017/maisi_db --authenticationDatabase admin
当直接登录数据库时,默认会到登录数据库中获取用户名和密码,而超级用户是存储在admin库中的,因此会因找不到用户而报错。可以使用--authenticationDatabase指定授权数据库。
[root@ecs ~]# mongosh maisi_db -u admin -p ay2ikfyYonmKvFkS --authenticationDatabase admin
查看所有数据库
admin> show dbs
admin 100.00 KiB
config 12.00 KiB
local 72.00 KiB
查看当前库下所有表
admin> show tables
system.users
system.version
切换test数据库
admin> use test
switched to db test
test>
创建数据库(不存在则创建,存在则切换数据库)
admin> use maisi_db
switched to db maisi_db
maisi_db>
删除数据库
maisi_db> db.dropDatabase()
{ ok: 1, dropped: 'maisi_db' }
maisi_db>
获取当前数据库名称
maisi_db> db
maisi_db
maisi_db> db.getName()
maisi_db
获取数据库信息
maisi_db> db.stats()
{
db: 'maisi_db',
collections: Long('0'),
views: Long('0'),
objects: Long('0'),
avgObjSize: 0,
dataSize: 0,
storageSize: 0,
indexes: Long('0'),
indexSize: 0,
totalSize: 0,
scaleFactor: Long('1'),
fsUsedSize: 0,
fsTotalSize: 0,
ok: 1
}
备份数据库
MongoDB导入导出工具从4.4版开始不再随数据库自动安装,而需手动安装MongoDB Database Tools,下载地址:https://www.mongodb.com/try/download/database-tools
[root@ecs ~]# tar -zxvf mongodb-database-tools-rhel70-x86_64-100.8.0.tgz
[root@ecs ~]# mv -uf mongodb-database-tools-rhel70-x86_64-100.8.0/bin/* /usr/local/mongodb/bin
[root@ecs ~]# echo "export PATH=\$PATH:/usr/local/mongodb/bin" >> /etc/profile
[root@ecs ~]# source /etc/profile
[root@ecs ~]# mkdir -p /data/backup
[root@ecs ~]# mongodb://root:'root'@127.0.0.1:27017/maisi_db --authenticationDatabase admin
maisi_db> db.fsyncLock()
另起一个终端:
[root@ecs ~]# mongodump -u root --password 'root' --authenticationDatabase admin -h 127.0.0.1 -d maisi_db -o /data/backup
或者:
[root@ecs ~]# mongodump --uri=mongodb://root:root@127.0.0.1:27017/maisi_db?authSource=admin -o /data/backup
等待备份完成,重新返回Mongo Shell,继续执行:
maisi_db> db.fsyncUnlock()
还原数据库
maisi_db> db.dropDatabase()
[root@ecs ~]# mongorestore -h 127.0.0.1:27017 --authenticationDatabase=admin -u root -p 'root' -d maisi_db /data/backup/maisi_db
或者:
[root@ecs ~]# mongorestore --drop -h 127.0.0.1:27017 --authenticationDatabase=admin -u root -p 'root' -d maisi_db /data/backup/maisi_db
注:还原数据库之前,必须先删除旧库或者添加--drop,否则在恢复数据库的时候,集合中遇到相同的_id会报错。
用户
创建超级权限root角色用户
admin> db.createUser({user: 'root', pwd: 'root', roles: [{role: 'root', db: 'admin'}]})
{ ok: 1 }
创建root级别的用户和管理员级别的用户都只能在admin数据库里面执行,创建指定数据库的所有者需要通过use切换数据库。另外,需要注意,系统级别的函数,比如:db.system.users.find()(查看系统所有用户),只能在admin数据库下执行,创建用户和执行其他系统函数的时候需要先使用db.auth鉴权。
添加最高级权限
admin> db.grantRolesToUser("root", [{ role: "__system", db: "admin"}])
创建库用户(必须先切换到对应库)
admin> db.auth('root', 'root')
admin> use maisi_db
maisi_db> db.createUser({user: 'maisi', pwd: 'kJTzjgSnIKqbZ4UD', roles: [{role: 'dbOwner', db: 'maisi_db'}]})
{ ok: 1 }
删除库用户
maisi_db> db.dropUser('maisi')
{ ok: 1 }
或者切换到admin库下操作db.system.users(必须切换到admin库,否则没有权限):
admin> db.system.users.deleteOne({user: 'maisi'})
{ acknowledged: true, deletedCount: 1 }
修改库用户密码
maisi_db> db.updateUser('maisi', {pwd: 'sCUKbQgHwI1VdUIw'})
{ ok: 1 }
或者:
maisi_db> db.changeUserPassword("maisi", "sCUKbQgHwI1VdUIw")
{ ok: 1 }
更改用户角色
maisi_db> db.updateUser('maisi', {roles: [{ role: "dbOwner", db: "maisi_db" }]})
{ ok: 1 }
查询所有用户(必须切换到admin)
admin> db.system.users.find()
查看当前数据库用户权限
maisi_db> show users
[
{
_id: 'maisi_db.maisi',
userId: UUID('8737b649-ce51-4de3-8dac-54296969f66e'),
user: 'maisi',
db: 'maisi_db',
roles: [ { role: 'dbOwner', db: 'maisi_db' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
]
maisi_db> db.getUsers()
{
users: [
{
_id: 'maisi_db.maisi',
userId: UUID('8737b649-ce51-4de3-8dac-54296969f66e'),
user: 'maisi',
db: 'maisi_db',
roles: [ { role: 'dbOwner', db: 'maisi_db' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
],
ok: 1
}
maisi_db> db.getUser('maisi')
{
_id: 'maisi_db.maisi',
userId: UUID('8737b649-ce51-4de3-8dac-54296969f66e'),
user: 'maisi',
db: 'maisi_db',
roles: [ { role: 'dbOwner', db: 'maisi_db' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
新增角色
maisi_db> db.grantRolesToUser("maisi", [{role: "userAdmin", db: "maisi_db"}])
{ ok: 1 }
删除角色
maisi_db> db.revokeRolesFromUser("maisi", [{role: "userAdmin", db: "maisi_db"}])
{ ok: 1 }
集合
查看所有集合
maisi_db> show tables
app_user
maisi_db> show collections
app_user
写入文档
mongo
> db.app_user.insert({username: 'alice'})
WriteResult({ "nInserted" : 1 })
mongosh
maisi_db> db.app_user.insertOne({username: 'bob', skill: ['电脑', '音乐']})
{
acknowledged: true,
insertedIds: { '0': ObjectId('6a62dc4b8378d289b0a26a14') }
}
查询文档
普通查询
mongo
> db.app_user.find()
{ "_id" : ObjectId("6a63707319c415e8b0f5d4de"), "username" : "bob", "skill" : [ "电脑", "音乐" ] }
{ "_id" : ObjectId("6a63708c19c415e8b0f5d4df"), "username" : "alice" }
mongosh
maisi_db> db.app_user.find()
[
{ _id: ObjectId('6a62d6368378d289b0a26a13'), username: 'alice' },
{
_id: ObjectId('6a62dc4b8378d289b0a26a14'),
username: 'bob',
skill: [ '电脑', '音乐' ]
}
]
隐藏字段
maisi_db> db.app_user.find({}, {username: 1, _id: 0})
[ { username: 'alice' }, { username: 'bob' } ]
限制数组字段元素个数
maisi_db> db.app_user.find({}, {"skill": {$slice: [0,1]}})
[
{ _id: ObjectId('6a62d6368378d289b0a26a13'), username: 'alice' },
{
_id: ObjectId('6a62dc4b8378d289b0a26a14'),
username: 'bob',
skill: [ '电脑' ]
}
]
$slice语法:
| | 解释 |
|---|
| $slice(n) | 返回数组前n个 |
| $slice(-n) | 返回数组后n个 |
| $slice(m, n) | 从数组第m+1个开始,返回前n个 |
| $slice(-m, n) | 从数组倒数第m个开始,返回前n个 |
条件查询
| 符号 | 说明 |
|---|
| $gt | 大于 |
| $gte | 大于或等于 |
| $lt | 小于 |
| $lte | 小于或等于 |
| $ne | 不等于 |
| $in | 在范围内 |
| $nin | 不在范围内 |
maisi_db> db.app_user.find({username: 'alice'})
maisi_db> db.app_user.find({age: {$gt: 20}})
maisi_db> db.app_user.find({age: {$gt: 20, $lt: 50}})
maisi_db> db.app_user.find({username: {$ne: 'alice'}})
maisi_db> db.app_user.find({username: {$in: ['alice', 'bob']}})
去重查询
maisi_db> db.app_user.distinct('username')
[ "alice", "bob" ]
maisi_db> db.app_user.distinct('username', {age: {$gt: 20}})
[ "alice" ]
正则查询
maisi_db> db.app_user.find({username:/^ali/})
maisi_db> db.app_user.find({username: {$regex: '^ali.*'}})
字段存在查询
maisi_db> db.app_user.find({age: {$exists: true}})
注:$exists只判断字段是否存在,不判断字段的值。
字段类型查询
maisi_db> db.app_user.find({username: {$type: 'string'}})
模查询
maisi_db> db.app_user.find({age: {$mod: [10, 2]}})
全文检索
maisi_db> db.app_user.createIndex({skill: 'text', hobby: 'text'})
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 2,
"numIndexesAfter" : 3,
"ok" : 1
}
maisi_db> db.app_user.find({$text: {$search: "电脑 游戏"}})
maisi_db> db.app_user.find({$text: {$search: "-电脑 游戏"}})
maisi_db> db.app_user.updateOne({username: 'alice'}, {$set: {hobby: 'watch TV,play game'}})
maisi_db> db.app_user.find({$text: {$search: "\"play game\""}})
maisi_db> db.app_user.find({$text: {$search: "电脑"}}, {score: {$meta: "textScore"}}).sort({score: {$meta: "textScore"}})
注:使用全文检索必须给文档创建text索引,一个文档只能有一个text索引,可以包含多个字段。
- 使用空格分隔搜索字符串,分割后的多个关键字执行逻辑或;
- 排除某个关键字,在关键字前加
-,-电脑表示排除文本索引中包含电脑的文档; - 短语查询通过双引号组合,双引号需要转义;
{$meta: "textScore"}计算并获取文档与检索关键字的相关性匹配得分。
高级查询
maisi_db> db.app_user.find({"$where": "this.username=='alice'"})
分页
maisi_db> db.app_user.find().limit(2).skip(0)
排序(1升序,-1降序)
maisi_db> db.app_user.find().sort({username: 1})
maisi_db> db.app_user.find().sort({username: -1})
逻辑或
maisi_db> db.app_user.find({$or: [{username: 'alice'}, {age: {$lt: 30}}]})
更新文档
maisi_db> db.app_user.updateOne({"username": "bob"}, {$set: {"username": "alice"}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
或者:
maisi_db> user = db.app_user.findOne({username: 'alice'})
{ _id: ObjectId('6a62d6368378d289b0a26a13'), username: 'alice' }
maisi_db> user.username = 'bob'
bob
maisi_db> db.app_user.replaceOne({_id: user._id}, user, {upsert: true})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
upsert: true:当找不到文档时,插入新文档。
注:mongosh已经废弃了save()方法,save()仅适用于mongo。
> user = db.app_user.findOne({username: 'alice'})
> user.username = 'bob'
> db.app_user.save(user)
删除文档
> db.app_user.remove({"username": "alice"}, {justOne: true, writeConcern: {w: 0}})
WriteResult({ })
justOne:是否只删除一个文档,默认为false,删除符合条件的所有文档,true则只删除一个文档。
maisi_db> db.app_user.deleteMany({"username": "alice"});
{ acknowledged: true, deletedCount: 1 }
调整文档结构
新增字段
> db.app_user.update({}, {$set: {age: ''}}, {multi: true})
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })
multi: true:默认为false,只更新匹配的第一个文档,true则更新匹配的所有文档。
maisi_db> db.app_user.updateMany({}, {$set: {age: ''}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
删除字段
> db.app_user.update({}, {$unset: {age: ''}}, {multi: true})
WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 2 })
索引
聚合
db.collection.aggregate()是基于数据处理的聚合管道,每个文档通过管道进行处理后输出结果。
基本语法:db.collection.aggregate(pipeline, options)
pipeline:一系列阶段,管道由多个阶段(stage)组成。阶段包括:count、match(筛选)、project(映射)、group(分组)、unwind、sort(排序)、limit(限制)、skip(跳过)、sortByCount、lookup、out和addFields等;options:可选,仅pipeline为数组时才能指定options。
$count
返回管道中的文档数。
maisi_db> db.app_user.aggregate([{$count: "total"}])
[ { total: 2 } ]
maisi_db> db.app_user.aggregate([{$match: {skill: {$regex: '电脑|游戏'}}}, {$count: 'total'}])
[ { total: 1 } ]
- $match阶段skill包含“电脑”或“游戏”的文档传入下个阶段;
- $count阶段返回管道中剩余文档的计数,并将该值分配给名为total的字段。
等效$group + $project:
maisi_db> db.app_user.aggregate([{$group: {_id: null, total: { $sum: 1 }}}, {$project: {_id: 0, total: 1}}])
[ { total: 2 } ]
$group
按指定的表达式对文档进行分组,并将分组后的文档输出到下一个阶段。
{$group: {_id: [expression], field: {accumulator: expression, ...}, ...}}
- _id必填,可以赋值null为整个输入文档计算累计值;
- 剩余计算字段可选,使用accumulator运算符进行计算;
- _id和accumulator表达式可以接受任意有效表达式。
accumulator
| 操作符 | 描述 |
|---|
| $avg | 计算均值 |
| $first | 返回每组第一个文档,组内按照排序规则(未指定则按照存储顺序)排序 |
| $last | 返回每组最后一个文档 |
| $max | 返回每组最大值 |
| $min | 返回每组最小值 |
| $push | 将表达式值添加到数组 |
| $addToSet | 将表达式值添加到集合(无重复值,无序) |
| $sum | 计算求和 |
| $stDevPop | 返回输入值总体标准偏差(population standard deviation) |
| $stDevSamp | 返回输入值样本标准偏差(the sample standard deviation) |
按年、月、日对订单进行分组,并计算每组总价、平均商品数和订单数
maisi_db> db.app_order.aggregate([{
$group: {
_id: {
year: {$year: "$order_time"},
month: {$month: "$order_time"},
day: {$dayOfMonth: "$order_time"}
},
total_price: {$sum: {$multiply: ["$price", "$quantity"]}},
avg_quantity: {$avg: "$quantity"},
total: {$sum: 1}
}
}])
[
{
_id: { year: 2026, month: 5, day: 15 },
total_price: 2241.6,
avg_quantity: 4,
total: 1
},
{
_id: { year: 2026, month: 6, day: 30 },
total_price: 8960,
avg_quantity: 8,
total: 1
}
]
计算所有订单的总价、平均商品数和订单数
maisi_db> db.app_order.aggregate([{
$group: {
_id: null,
total_price: {$sum: {$multiply: ["$price", "$quantity"]}},
avg_quantity: {$avg: "$quantity"},
total: {$sum: 1}
}
}])
[
{ _id: null, total_price: 144997.95, avg_quantity: 4.35, total: 40 }
]
获取所有订单日期
maisi_db> db.app_order.aggregate([{
$group: {
_id: {
$dateToString: {
format: "%Y-%m-%d",
date: "$order_time",
timezone: "+08:00"
}
}
}
}])
[
{ _id: '2026-05-11' }, { _id: '2026-07-08' }
]
按日期分组统计订单总数并返回订单总数大于10的分组
mais_db> db.app_order.aggregate([{
$group: {
_id: {
order_date: {
year: { $year: "$order_time" },
month: { $month: "$order_time" },
day: { $dayOfMonth: "$order_time" }
}
},
total_quantity: { $sum: "$quantity" }
}
}, {
$match: {
total_quantity: { $gt: 10 }
}
}])
[
{
_id: { order_date: { year: 2026, month: 6, day: 18 } },
total_quantity: 12
}
]
将6月份的订单按日期分组统计分组数
maisi_db> db.app_order.aggregate([{
$match: {
order_time: {
$gte: ISODate("2026-05-31T16:00:00Z"),
$lt: ISODate("2026-06-30T16:00:00Z")
}
}
}, {
$group: {
_id: {
order_date: {
year: { $year: "$order_time" },
month: { $month: "$order_time" },
day: { $dayOfMonth: "$order_time" }
}
}
}
}, {
$group: {
_id: null,
count: { $sum: 1 }
}
}])
[ { _id: null, count: 15 } ]
按用户性别分组,并在每组显示用户名列表和用户列表
maisi_db> db.app_user.aggregate([{
$group: {
_id: "$gender",
usernames: {
$push: "$username"
},
users: {
$push: "$$ROOT"
}
}
}])
[
{
_id: 2,
usernames: [ 'Cindy', 'Grace', 'Helen' ],
users: [
{
_id: ObjectId('6a63d5476077589d45abd7fd'),
username: 'Cindy',
gender: 2,
age: 13
},
{
_id: ObjectId('6a63d5476077589d45abd801'),
username: 'Grace',
gender: 2,
age: 9
},
{
_id: ObjectId('6a63d5476077589d45abd802'),
username: 'Helen',
gender: 2,
age: 14
}
]
},
{
_id: 1,
usernames: [ 'Alice', 'Bob', 'Dale', 'Eric', 'Frank' ],
users: [
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16
},
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1,
age: 11
},
{
_id: ObjectId('6a63d5476077589d45abd7fe'),
username: 'Dale',
gender: 1,
age: 10
},
{
_id: ObjectId('6a63d5476077589d45abd7ff'),
username: 'Eric',
gender: 1,
age: 8
},
{
_id: ObjectId('6a63d5476077589d45abd800'),
username: 'Frank',
gender: 1,
age: 18
}
]
}
]
按用户性别分组,获取每组最大年龄和最小年龄
maisi_db> db.app_user.aggregate([{
$group: {
_id: "$gender",
min_age: { $min: "$age" },
max_age: { $max: "$age" }
}
}])
[
{ _id: 1, min_age: 8, max_age: 18 },
{ _id: 2, min_age: 9, max_age: 14 }
]
按用户性别分组,或每组第一个和最后一个用户
maisi_db> db.app_user.aggregate([{
$group: {
_id: "$gender",
first: { $first: "$username" },
last: { $last: "$username" }
}
}])
[
{ _id: 1, first: 'Alice', last: 'Frank' },
{ _id: 2, first: 'Cindy', last: 'Helen' }
]
$match
过滤文档,仅将符合条件的文档传递到新一个阶段。
$match查询语法与find()查询语法相同。
简单匹配查询
maisi_db> db.app_user.aggregate([{
$match: {
username: "Alice"
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16
}
]
将年龄10-15的用户按性别统计人数
maisi_db> db.app_user.aggregate([{
$match: {
age: { $gte: 10, $lte: 15 }
}
}, {
$group: {
_id: "$gender",
count: { $sum: 1 }
}
}])
[ { _id: 1, count: 2 }, { _id: 2, count: 2 } ]
$unwind
将文档数组拆分成单独的文档。
示例文档:
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: [ '电脑', '游戏' ]
}
]
使用$unwind将skill数组中每个元素单独输出文档
maisi_db> db.app_user.aggregate([{
$match: { username: "Alice" }
}, {
$unwind: "$skill"
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '电脑'
},
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '游戏'
}
]
显示数组元素的索引
maisi_db> db.app_user.aggregate([{
$match: { username: "Alice" }
}, {
$unwind: {
path: "$skill",
includeArrayIndex: "index"
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '电脑',
index: Long('0')
},
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '游戏',
index: Long('1')
}
]
默认情况下只输出skill有值的文档,如果需要输出不存在skill字段、skill值为null或空数组的文档,可以设置preserveNullAndEmptyArrays为true。
maisi_db> db.app_user.aggregate([{
$unwind: {
path: "$skill",
preserveNullAndEmptyArrays: true
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '电脑',
index: Long('0')
},
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: '游戏',
index: Long('1')
},
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1,
age: 11
}
]
$project
选择字段、重命名字段和派生字段。
输出文档只包含username
maisi_db> db.app_user.aggregate([{
$project: {
username: 1
}
}])
[
{ _id: ObjectId('6a63d5476077589d45abd7fb'), username: 'Alice' },
{ _id: ObjectId('6a63d5476077589d45abd7fc'), username: 'Bob' },
{ _id: ObjectId('6a63d5476077589d45abd7fd'), username: 'Cindy' },
{ _id: ObjectId('6a63d5476077589d45abd7fe'), username: 'Dale' },
{ _id: ObjectId('6a63d5476077589d45abd7ff'), username: 'Eric' },
{ _id: ObjectId('6a63d5476077589d45abd800'), username: 'Frank' },
{ _id: ObjectId('6a63d5476077589d45abd801'), username: 'Grace' },
{ _id: ObjectId('6a63d5476077589d45abd802'), username: 'Helen' }
]
默认会包含_id字段,设置_id为0以排除_id字段:
maisi_db> db.app_user.aggregate([{
$project: {
_id: 0,
username: 1
}
}])
[
{ username: 'Alice' },
{ username: 'Bob' },
{ username: 'Cindy' },
{ username: 'Dale' },
{ username: 'Eric' },
{ username: 'Frank' },
{ username: 'Grace' },
{ username: 'Helen' }
]
输出排除age字段
maisi_db> db.app_user.aggregate([{
$project: {
age: 0
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
skill: [ '电脑', '游戏' ]
},
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1
}
]
排除嵌套文档字段
maisi_db> db.app_user.aggregate([{
$project: {
"region.province": 0
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: [ '电脑', '游戏' ],
region: { city: '南京市' }
}
]
maisi_db> db.app_user.aggregate([{
$project: {
region: {
province: 0
}
}
}])
3.6+可以使用REMOVE进行条件移除
maisi_db> db.app_user.aggregate([{
$project: {
"region.city": {
$cond: {
if: { $eq: ["南京市", "$region.city"] },
then: "$$REMOVE",
else: "$$ROOT"
}
}
}
}])
返回计算后字段
maisi_db> db.app_user.aggregate([{
$project: {
province: { $substr: [ "$id_card_no", 0, 2] },
city: { $substr: [ "$id_card_no", 2, 2] },
district: { $substr: [ "$id_card_no", 4, 2] },
birthday: { $substr: [ "$id_card_no", 6, 8] },
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
province: '13',
city: '23',
district: '30',
birthday: '19800101'
}
]
$limit
限制传递到下一个阶段的文档数
maisi_db> db.app_user.aggregate([{
$limit: 2
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: [ '电脑', '游戏' ],
region: { province: '江苏省', city: '南京市' },
id_card_no: '132330198001011211'
},
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1,
age: 11
}
]
$skip
跳过指定数量的文档,将剩余文档传递给下一个阶段
maisi_db> db.app_user.aggregate([{
$skip: 1
}, {
$limit: 2
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1,
age: 11
},
{
_id: ObjectId('6a63d5476077589d45abd7fd'),
username: 'Cindy',
gender: 2,
age: 13
}
]
$sort
对输入文档进行排序(1升序,-1降序),将排序后的结果传递个下一个阶段
maisi_db> db.app_user.aggregate([{
$sort: { age: 1 }
}, {
$skip: 1
}, {
$limit: 2
}])
[
{
_id: ObjectId('6a63d5476077589d45abd801'),
username: 'Grace',
gender: 2,
age: 9
},
{
_id: ObjectId('6a63d5476077589d45abd7fe'),
username: 'Dale',
gender: 1,
age: 10
}
]
$sortByCount
根据表达式的值对文档进行分组,统计各组文档数量,根据文档数量降序排列,输出_id和count值。
maisi_db> db.app_user.aggregate([{
$sortByCount: "$gender"
}])
[ { _id: 1, count: 5 }, { _id: 2, count: 3 } ]
$lookup
maisi_db> db.app_user.aggregate([{
$lookup: {
from: "app_profile",
localField: "_id",
foreignField: "user_id",
as: "profile"
}
}])
[
{
_id: ObjectId('6a63d5476077589d45abd7fb'),
username: 'Alice',
gender: 1,
age: 16,
skill: [ '电脑', '游戏' ],
region: { province: '江苏省', city: '南京市' },
id_card_no: '132330198001011211',
profile: [
{
_id: ObjectId('6a63fd462ba0b4fd98b28930'),
user_id: ObjectId('6a63d5476077589d45abd7fb'),
avatar: 'https://i0.hdslb.com/bfs/face/94f8acdde8d111f6d9cc6fe9d450a7c90cd337b8.jpg'
}
]
},
{
_id: ObjectId('6a63d5476077589d45abd7fc'),
username: 'Bob',
gender: 1,
age: 11,
profile: []
}
]
localField:主表(app_user)中的字段;foreignField:关联表(app_profile)中的字段。
安全
角色
| 角色类型 | 角色 | 描述 |
|---|
数据库一般角色 Database User Roles | read | 可以读取所有非系统集合和部分系统集合(system.indexes,system.js和system.namespaces)。 允许调用:collStats、dbHash、dbStats、find、killCursors、listIndexes和listCollections。 |
| | readWrite | 可以读取所有集合,写入所有非系统集合和写入部分系统集合(system.js)。 允许调用:collStats、convertToCapped、createCollection、dbHash、dbStats、dropCollection、createIndex、dropIndex、find、emptycapped、insert、killCurrsors、listIndexes、listCollections、remove、renameCollectionSameDB和update。 |
数据库管理角色 Database Administration Roles | dbAdmin | 可以在当前数据库上执行管理函数或部分系统集合上执行部分函数。 允许在system.indexes, system.namespaces, system.profile调用:collStats、dbHash、dbStats、find、killCursors、listIndexes、listCollections。 system.profile还允许调用:dropCollection和createCollection。 在非系统集合允许调用:collMod、collStats、compact、convertToCapped、createCollection、createIndex、dbStats、dropCollection、dropDatabase、dropIndex、enableProfiler、indexStats、reIndex、renameCollectionSameDB、repairDatabase、storageDetails和validate。 |
| | userAdmin | 提供在当前数据库上创建和修改用户和角色的能力。拥有该角色的用户可以给其他用户授予任何权限(包括userAdmin角色)。 允许调用:changeCustomData、changePassword、createRole、createUser、dropRole、dropUser、grantRole、revokeRole、viewRole和viewUser。 |
| | dbOwner | 提供对数据库执行任何管理操作的能力。此角色集合了readWrite,dbAdmin和userAdmin角色授予的权限。 |
全数据库角色 All-Database Roles | readAnyDatabase | 在集群的所有数据库上(除local和config数据库)提供与read角色相同的权限,同时提供listDatabases命令权限。 |
| | readWriteAnyDatabase | 在集群的所有数据库上提供与readWrite角色相同的权限,同时提供listDatabases命令权限。 |
| | userAdminAnyDatabase | 在集群中的所有数据库上提供和userAdmin角色相同的权限,此外还提供authSchemaUpgrade、invalidateUserCache和listDatabases权限。此角色还可以在admin数据库的system.users和system.roles执行:collStats、dbHash、dbStats、find、killCursors、planCacheRead、createIndex和dropIndex。 |
| | dbAdminAnyDatabase | 在集群的所有数据库上提供与dbAdmin角色相同的权限,同时提供listDatabases命令权限。 |
集群管理角色 Cluster Administration Roles | clusterManager | 提供对群集的管理和监视操作。具有此角色的用户可以访问local和config数据库,分别用于分片和复制。 允许调用:addShard、applicationMessage、cleanupOrphaned、flushRouterConfig、listShards、removeShard、replSetConfigure、replSetGetStatus、replSetStateChange、resync、enableSharding、moveChunk、splitChunk和splitVector。 config数据库还允许调用:insert、remove、update、collStats、dbHash、find和killCursors。 |
| | clusterMonitor | 为监控工具(如MongoDB Cloud Manager或Ops Manager监控代理)提供只读访问权限。 允许调用:connPollStats、cursorInfo、getCmdLineOpts、getLog、getParameter、getShardMap、hostInfo、inprog、listDatabases、lisstShards、netstat、replSetGetStatus、serverStatus、shardingState、top、collStats、dbStats和getShardVersion。 |
| | hostManager | 提供监视和管理服务器的能力。 允许调用:applicationMessage、closeAllDatabases、connPoolSync、cpuProfiler、diagLogging、flushRouterConfig、fsync、invalidateUserCache、killop、logRotate、resync、setParameter、showdown、touch和unlock。 |
| | clusterAdmin | 提供群集管理的最高权限。此角色集合了clusterManager、clusterMonitor和hostManager,还拥有dropDatabase操作权限。 |
备份和恢复 Backup and Restoration Roles | backup | 提供备份数据所需的权限。此角色提供足够的权限来使用MongoDB Cloud Manager备份代理,Ops Manager备份代理或使用mongodump |
| | restore | 提供足够的权限来使用mongorestore |
超级角色 Superuser Roles | root | 提供对所有资源的访问权限。集合了readWriteAnyDatabase、dbAdminAnyDatabase、userAdminAnyDatabase、clusterAdmin、backup和restore。 |
| 内部角色 | __system | 该角色允许用户对所有数据库拥有所有操作权限,不要将该角色授予应用程序用户或其它管理员用户。 |
查看某个角色在指定数据库上所拥有的详细权限:
admin> db.runCommand({rolesInfo: {role: "dbAdmin", db: "maisi_db"}, showPrivileges: true})
Write Concern
MongoDB Write Concern(MongoDB写入安全机制)是一种客户端设置,用于控制写入安全的级别。Write Concern描述了MongoDB写入到mongod单实例、副本集以及分片集群时何时应答给客户端。默认情况下,mongoDB文档增删改都会一直等待数据库响应(确认写入是否成功),然后才会继续执行。
MongoDB应答机制就是对于当前数据库的写入成功与否告知客户端(db.getLastError())。mongoDB Client发出写入(或更新)请求后,mongoDB Server端开始写入,然后通知客户端是否写入成功。应答机制主要分两种:
- 应答式写入,缺省情形,安全写入,适用于数据强一致性场景;
- 非应答式写入,非安全写入,适用于数据弱一致性场景。
MongoDB通过Write Concern,客户端调用db.getLastError()方法来获取服务端的返回信息,如果捕获到错误,则可以尝试再次写入或记录到特定日志等。
Write Concern的用法:{ w: <value>, j: <boolean>, wtimeout: <number> }。
w用以指定写入操作需要确认的节点数量:
w: 0(Unacknowledged),非应答式写入,客户端不等待服务器的任何确认。

w: 1(Acknowledged,默认值),应答式写入,要求主节点确认写入。

对于使用副本集的场景,默认情况下仅仅从主节点进行应答,建议修改缺省的应答情形为特定数量或者majority来保证数据的可靠。

- w: n(大于1的整数),要求主节点以及至少n-1个从节点确认写入。
- w: "majority",要求写入操作被副本集中的大多节点确认。
- w: "自定义",要求写入操作被副本集中指定TAG标记的节点确认。
j要求确认写入journal日志后应答客户端。

注:开启j选项前,必须开启mongodb的日志功能。
wtimeout指定时间限制,防止写操作被阻塞导致无法应答给客户端,单位为ms,当w大于1时自动生效。
仅适用于集群环境,当某个节点写入超出wtimeout后,mongod返回错误。
小结:
- write concern用于控制写入安全的级别,可以分为应答式写入和非应答式写入;
- write concern是一个性能和数据强一致性的权衡,应根据业务场景进行设定;
- 对于强一致性场景,建议w>1或者等于majority,同时j为true,否则w=0;
- 在副本集的情形下,建议通过配置文件来修改w和wtimeout,以避免由于某个节点挂起导致无法应答。
常见问题
Authorization failed
MongoDB自3.0.3版本之后加入了SCRAM-SHA-1校验方式,之前默认使用MONGODB-CR验证方式。部分客户端端软件在使用用户名和密码连接MongoDB时,默认采用MONGODB-CR校验方式,从而导致授权失败了。
查看验证方式:
admin> db.system.version.find()
{ "_id" : "featureCompatibilityVersion", "version" : "4.2" }
{ "_id" : "authSchema", "currentVersion" : 5 }
authSchema版本为5,即SCRAM-SHA-1校验方式,将authSchema改为3,切换为MONGODB-CR校验方式。
解决方法(二选一):
- 升级客户端,使用SCRAM-SHA-1校验方式;
- 使用低版本MongoDB。