MongoDB 基础教程
实验环境 :华为云 ECS ecs-ff10-0002 · Ubuntu 24.04.4 LTS · MongoDB 8.0.26
实操时间 :2026-06-20
知识体系:数据库/集合基本操作 → 数据查询 → 文档操作 → 聚合与索引 → 高级查询与高级索引
一、MongoDB 概念
1.1 什么是 MongoDB
MongoDB 是一款面向文档的 NoSQL 数据库,数据以 BSON(Binary JSON)格式存储。与关系型数据库相比,它具有以下核心特点:
| 维度 | 关系型数据库(MySQL) | MongoDB |
|---|---|---|
| 数据模型 | 行与列(表格) | 文档(JSON/BSON) |
| Schema | 固定结构 | 灵活可变 |
| 扩展方式 | 垂直扩展为主 | 原生水平扩展(分片) |
| 查询语言 | SQL | MQL(Mongo Query Language) |
| 事务 | 完整 ACID | 多文档事务(4.x+) |
| 适用场景 | 强一致性业务 | 高并发/非结构化/快速迭代 |
1.2 核心术语对照
| MySQL | MongoDB | 说明 |
|---|---|---|
| database | database | 数据库 |
| table | collection | 集合 |
| row | document | 文档 |
| column | field | 字段 |
| index | index | 索引 |
| JOIN | $lookup |
关联查询 |
| primary key | _id |
主键(自动生成 ObjectId) |
1.3 BSON 数据类型
MongoDB 支持丰富的数据类型:
String 字符串
Integer 整数(32/64 位)
Double 浮点数
Boolean 布尔值
Date 日期时间
Array 数组
Object 嵌套文档
ObjectId 12 字节唯一标识符
Null 空值
Regex 正则表达式
BinData 二进制数据
二、安装 MongoDB
2.1 添加官方 apt 源(Ubuntu 24.04)
bash
# 1. 导入 GPG 公钥
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc \
| gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor
# 2. 添加软件源
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" \
| tee /etc/apt/sources.list.d/mongodb-org-8.0.list
# 3. 更新包列表并安装
apt-get update
apt-get install -y mongodb-org
2.2 启动服务并验证
bash
systemctl enable mongod
systemctl start mongod
systemctl status mongod
实操输出:
● mongod.service - MongoDB Database Server
Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-06-20 12:03:38 CST; 2s ago
Main PID: 9677 (mongod)
bash
mongod --version
db version v8.0.26
Build Info: {
"version": "8.0.26",
"openSSLVersion": "OpenSSL 3.0.13 30 Jan 2024",
"distmod": "ubuntu2404",
"distarch": "x86_64"
}
2.3 连接 MongoDB Shell
bash
mongosh # 连接默认本地实例
mongosh school # 直接进入 school 数据库
mongosh "mongodb://host:27017/db" # 连接远程
三、数据库与集合基本操作
3.1 数据库操作
javascript
// 查看所有数据库
show dbs
// 切换/创建数据库(不存在则自动创建,插入数据后才真正落盘)
use school
// 查看当前数据库
db
// 查看数据库列表(带大小)
db.adminCommand({listDatabases:1}).databases.forEach(d =>
print(d.name + " " + d.sizeOnDisk + " bytes")
)
实操输出:
admin 40KB
config 12KB
local 40KB
school 196KB
javascript
// 删除数据库(在目标库内执行)
db.dropDatabase()
3.2 集合操作
javascript
// 显式创建集合
db.createCollection('students')
db.createCollection('courses')
// 查看集合列表
show collections
db.getCollectionNames()
// 删除集合
db.students.drop()
// 重命名集合
db.courses.renameCollection('course_info')
// 集合统计信息
db.runCommand({collStats: "students"})
实操输出:
文档数: 8 大小: 1783 bytes 索引数: 5
提示 :插入第一条文档时集合会自动创建,
createCollection()用于需要指定选项(如 capped 固定集合)的场景。
四、文档基本操作(CRUD)
4.1 插入文档
insertOne ------ 插入单条
javascript
var result = db.students.insertOne({
name: '张三',
gender: 'M',
age: 20,
score: 88.5,
hobbies: ['篮球', '编程'], // Array 类型
address: { city: '北京', district: '海淀' }, // 嵌套文档
enrolled: true, // Boolean
createAt: new Date('2026-06-20') // Date
})
print(result.insertedId) // ObjectId("6a361159ccbd98a6a59df8a3")
insertMany ------ 批量插入
javascript
db.students.insertMany([
{ name: '李四', gender: 'F', age: 19, score: 92.0,
hobbies: ['阅读','音乐'], address: { city: '上海', district: '浦东' },
enrolled: true, createAt: new Date('2026-06-20') },
{ name: '王五', gender: 'M', age: 21, score: 75.3,
hobbies: ['游泳'], address: { city: '广州', district: '天河' },
enrolled: true, createAt: new Date('2026-06-20') },
{ name: '赵六', gender: 'F', age: 20, score: 96.8,
hobbies: ['数学','编程'], address: { city: '深圳', district: '南山' },
enrolled: true, createAt: new Date('2026-06-20') },
// ... 更多文档
])
实操结果:total docs: 8
4.2 更新文档
updateOne / updateMany
javascript
// $set ------ 更新指定字段
var r = db.students.updateOne(
{ name: '张三' }, // 查询条件
{ $set: { score: 91.0, remark: '班长' } } // 更新操作
)
// updateOne matched:1 modified:1
// 批量更新:给所有 age<=19 的学生加 grade 字段
db.students.updateMany(
{ age: { $lte: 19 } },
{ $set: { grade: '大一' } }
)
// updateMany matched:2 modified:2
常用更新操作符
| 操作符 | 说明 | 示例 |
|---|---|---|
$set |
设置字段值 | {$set: {score: 90}} |
$unset |
删除字段 | {$unset: {remark: ""}} |
$inc |
数值递增/递减 | {$inc: {score: 5}} |
$push |
向数组追加元素 | {$push: {hobbies: "钢琴"}} |
$pull |
从数组删除元素 | {$pull: {hobbies: "游泳"}} |
$addToSet |
追加(去重) | {$addToSet: {tags: "新"}} |
$rename |
重命名字段 | {$rename: {old: "new"}} |
实操演示:
javascript
// $inc: 王五成绩 +5
db.students.updateOne({name: '王五'}, {$inc: {score: 5.0}})
// 王五 score after +5: 80.3
// $push: 赵六爱好添加钢琴
db.students.updateOne({name: '赵六'}, {$push: {hobbies: '钢琴'}})
// 赵六 hobbies: ["数学","编程","钢琴"]
replaceOne ------ 替换整个文档
javascript
db.students.replaceOne(
{ name: '吴九' },
{ name: '吴九', gender: 'M', age: 20, score: 73.5,
hobbies: ['足球'], address: { city: '南京', district: '玄武' },
enrolled: true, remark: '成绩提升了' }
)
// 吴九 after replace: {"name":"吴九","score":73.5,"remark":"成绩提升了"}
findOneAndUpdate ------ 原子查询+更新
javascript
var doc = db.students.findOneAndUpdate(
{ name: '张三' },
{ $set: { remark: '优秀班长' } },
{ returnDocument: 'after', projection: {_id:0, name:1, remark:1} }
)
// { name: '张三', remark: '优秀班长' }
4.3 删除文档
javascript
// deleteOne ------ 删除第一条匹配文档
db.students.deleteOne({ name: '临时测试' })
// before delete count:9 → after deleteOne count:8
// deleteMany ------ 批量删除
db.students.deleteMany({ enrolled: false })
// 清空集合(保留集合定义和索引)
db.students.deleteMany({})
五、数据查询
5.1 基础查询
javascript
// 查询所有文档
db.students.find()
// 条件查询
db.students.find({ gender: 'F' })
// 投影:只返回指定字段(1=显示,0=隐藏,_id 默认显示需显式排除)
db.students.find({}, { _id: 0, name: 1, score: 1 })
实操输出:
张三 91
李四 92
王五 80.3
赵六 96.8
孙七 60
周八 85
吴九 73.5
郑十 99
5.2 比较操作符
| 操作符 | 含义 | 示例 |
|---|---|---|
$eq |
等于 | {score: {$eq: 92}} |
$ne |
不等于 | {enrolled: {$ne: false}} |
$gt |
大于 | {score: {$gt: 90}} |
$gte |
大于等于 | {age: {$gte: 20}} |
$lt |
小于 | {score: {$lt: 70}} |
$lte |
小于等于 | {age: {$lte: 19}} |
$in |
在列表中 | {age: {$in: [19, 21, 22]}} |
$nin |
不在列表中 | {city: {$nin: ["北京"]}} |
javascript
// score >= 90
db.students.find({score: {$gte: 90}}, {_id:0, name:1, score:1})
// 李四 92 / 赵六 96.8 / 郑十 99
// age 在 19~20 之间
db.students.find({age: {$gte: 19, $lte: 20}}, {_id:0, name:1, age:1})
// 张三 20 / 李四 19 / 赵六 20 / 周八 19 / 吴九 20
// age IN [19, 21, 22]
db.students.find({age: {$in: [19, 21, 22]}}, {_id:0, name:1, age:1})
5.3 逻辑操作符
javascript
// AND(隐式写法:多条件同一文档)
db.students.find({gender: 'M', score: {$gt: 80}})
// 张三 M 91
// $and(显式写法,可含复杂逻辑)
db.students.find({
$and: [{age: {$gte: 20}}, {score: {$gte: 80}}, {enrolled: true}]
})
// 张三 / 王五 / 赵六 / 郑十
// $or:满足任一条件
db.students.find({
$or: [{score: {$lt: 70}}, {score: {$gt: 95}}]
})
// 赵六 96.8 / 孙七 60 / 郑十 99
// $nor:均不满足(性别非 F 且分数 < 90)
db.students.find({
$nor: [{gender: 'F'}, {score: {$gte: 90}}]
})
// 王五 / 孙七 / 吴九
// $not:取反
db.students.find({score: {$not: {$gte: 90}}})
// 王五 / 孙七 / 周八 / 吴九(score < 90)
5.4 排序、分页
javascript
// 按 score 降序排序
db.students.find({}, {_id:0, name:1, score:1}).sort({score: -1})
// 郑十 99 → 赵六 96.8 → 李四 92 → 张三 91 → ...
// 分页:第 2 页(每页 3 条,跳过前 3 条)
db.students.find({}, {_id:0, name:1, score:1})
.sort({score: -1})
.skip(3)
.limit(3)
// { name: '张三', score: 91 }
// { name: '周八', score: 85 }
// { name: '王五', score: 80.3 }
5.5 嵌套文档与数组查询
javascript
// 嵌套字段查询(点表示法)
db.students.find({"address.city": "北京"}, {_id:0, name:1, "address.city":1})
// 张三(北京·海淀)/ 孙七(北京·朝阳)
// 数组包含某元素
db.students.find({hobbies: "编程"}, {_id:0, name:1, hobbies:1})
// 张三 / 赵六 / 郑十
// $in 查询数组包含任意一个
db.students.find({hobbies: {$in: ["编程", "数学"]}})
// $size:数组长度等于 1
db.students.find({hobbies: {$size: 1}}, {_id:0, name:1, hobbies:1})
// 王五 ["游泳"] / 孙七 ["游戏"] / 吴九 ["足球"]
5.6 其他查询
javascript
// 正则表达式(name 以 张 开头)
db.students.find({name: /^张/})
// $exists:字段是否存在
db.students.find({remark: {$exists: true}}, {_id:0, name:1, remark:1})
// 张三(优秀班长)/ 吴九(成绩提升了)
// $type:字段类型
db.students.find({score: {$type: "double"}})
// $expr:字段间比较(score > age * 4)
db.students.find({$expr: {$gt: ["$score", {$multiply: ["$age", 4]}]}})
// 张三 / 李四 / 赵六 / 周八 / 郑十
// distinct:获取字段唯一值
db.students.distinct("address.city")
// ["上海", "北京", "南京", "广州", "成都", "杭州", "深圳"]
// 文档计数
db.students.countDocuments({score: {$gte: 80}}) // 6(精确,带过滤条件)
db.students.estimatedDocumentCount() // 8(快速,全集合估算)
六、索引
6.1 索引类型
| 类型 | 创建语法 | 说明 |
|---|---|---|
| 单字段索引 | createIndex({score: 1}) |
升序(-1 降序) |
| 复合索引 | createIndex({gender:1, score:-1}) |
多字段联合索引 |
| 唯一索引 | createIndex({name:1}, {unique:true}) |
值不可重复 |
| 文本索引 | createIndex({name:"text", remark:"text"}) |
全文搜索 |
| TTL 索引 | createIndex({expireAt:1}, {expireAfterSeconds:0}) |
到期自动删除 |
| 2dsphere | createIndex({location:"2dsphere"}) |
地理空间查询 |
6.2 索引操作实操
javascript
// 创建单字段索引
db.students.createIndex({score: 1})
// 创建复合索引
db.students.createIndex({gender: 1, score: -1})
// 创建唯一索引
db.students.createIndex({name: 1}, {unique: true})
// 查看所有索引
db.students.getIndexes().forEach(idx =>
print(JSON.stringify({name: idx.name, key: idx.key, unique: idx.unique || false}))
)
实操输出:
json
{"name":"_id_","key":{"_id":1},"unique":false}
{"name":"score_1","key":{"score":1},"unique":false}
{"name":"gender_1_score_-1","key":{"gender":1,"score":-1},"unique":false}
{"name":"name_1","key":{"name":1},"unique":true}
javascript
// 删除指定索引
db.students.dropIndex({score: 1})
// 删除全部索引(除 _id)
db.students.dropIndexes()
6.3 EXPLAIN 验证索引
javascript
var expl = db.students.find({score: {$gte: 90}}).explain("executionStats")
print("winningPlan stage: " + expl.queryPlanner.winningPlan.stage)
print("totalDocsExamined: " + expl.executionStats.totalDocsExamined)
print("nReturned: " + expl.executionStats.nReturned)
实操输出:
winningPlan stage: FETCH
totalDocsExamined: 4
nReturned: 4
解读 :FETCH 阶段使用了
score_1索引,仅扫描 4 条文档即返回 4 条结果,避免全集合扫描(COLLSCAN)。
6.4 文本索引
javascript
// 创建文本索引(支持中文需配置分词器,默认英文分词)
db.students.createIndex({name: "text", remark: "text"})
// $text 全文搜索
db.students.find({$text: {$search: "班长"}}, {_id:0, name:1, remark:1})
6.5 TTL 自动过期索引
javascript
db.sessions.createIndex(
{createdAt: 1},
{expireAfterSeconds: 3600} // 1小时后自动删除
)
实操(courses 集合):
javascript
db.courses.createIndex({expireAt: 1}, {expireAfterSeconds: 0})
// TTL 索引已创建(expireAfterSeconds=0,到期自动删除)
// expireAt_1 expire=0
6.6 地理空间索引(2dsphere)
javascript
// 存储 GeoJSON 格式的坐标
db.students.updateOne({name: '张三'}, {
$set: {location: {type: "Point", coordinates: [116.397, 39.917]}}
})
db.students.createIndex({location: "2dsphere"})
// $near 查询:北京天安门周边 5km 范围内的学生
db.students.find({
location: {
$near: {
$geometry: {type: "Point", coordinates: [116.4, 39.9]},
$maxDistance: 5000
}
}
})
// 北京周边5km范围: ["张三"]
七、聚合框架
7.1 聚合管道概念
db.collection.aggregate([
{ $stage1: {...} }, // 第一个管道阶段
{ $stage2: {...} }, // 第二个管道阶段
...
])
文档在管道中依次流经各阶段,每个阶段对数据进行转换。
7.2 常用聚合阶段
| 阶段 | 说明 |
|---|---|
$match |
过滤(等同 WHERE) |
$group |
分组统计 |
$sort |
排序 |
$limit |
限制条数 |
$skip |
跳过 N 条 |
$project |
投影/字段计算 |
$unwind |
展开数组 |
$lookup |
左连接(关联查询) |
$count |
计数 |
$addFields |
添加/覆盖字段 |
7.3 聚合实操
$group ------ 分组统计
javascript
db.students.aggregate([
{$group: {
_id: "$gender",
count: {$sum: 1},
avgScore: {$avg: "$score"},
maxScore: {$max: "$score"}
}}
])
输出:
json
{ "_id": "F", "count": 4, "avgScore": 93.2, "maxScore": 99 }
{ "_id": "M", "count": 4, "avgScore": 76.2, "maxScore": 91 }
match + group ------ 条件聚合
javascript
db.students.aggregate([
{$match: {enrolled: true}},
{$group: {_id: null, count: {$sum: 1}, avg: {$avg: "$score"}}}
])
// { _id: null, count: 7, avg: 88.23 }
sort + limit ------ Top N
javascript
db.students.aggregate([
{$sort: {score: -1}},
{$limit: 3},
{$project: {_id:0, name:1, score:1}}
])
// 郑十 99 → 赵六 96.8 → 李四 92
project + cond ------ 计算字段
javascript
db.students.aggregate([
{$project: {
_id: 0,
name: 1,
score: 1,
level: {
$cond: [
{$gte: ["$score", 90]}, "优秀",
{$cond: [{$gte: ["$score", 75]}, "良好", "待提升"]}
]
}
}}
])
输出:
张三 91 优秀 / 李四 92 优秀 / 王五 80.3 良好
赵六 96.8 优秀 / 孙七 60 待提升 / 周八 85 良好
吴九 73.5 待提升 / 郑十 99 优秀
$unwind ------ 展开数组统计爱好
javascript
db.students.aggregate([
{$unwind: "$hobbies"},
{$group: {_id: "$hobbies", count: {$sum: 1}}},
{$sort: {count: -1}},
{$limit: 5}
])
输出:
编程×3 / 数学×2 / 篮球×1 / 足球×1 / 音乐×1
$lookup ------ 集合关联
javascript
// 查询学生及其选课信息
db.students.aggregate([
{$lookup: {
from: "course_info", // 关联集合
localField: "courseId", // 本集合字段
foreignField: "_id", // 目标集合字段
as: "courseDetail" // 输出字段名
}}
])
八、高级查询
8.1 逻辑组合进阶
javascript
// $and 显式写法(多条件)
db.students.find({
$and: [{age: {$gte: 20}}, {score: {$gte: 80}}, {enrolled: true}]
})
// $nor:所有条件均不满足
db.students.find({
$nor: [{gender: "F"}, {score: {$gte: 90}}]
})
// $not 对单个条件取反
db.students.find({score: {$not: {$gte: 90}}})
8.2 $expr ------ 字段间表达式比较
javascript
// 找出 score > age * 4 的学生(用表达式比较字段)
db.students.find({
$expr: {$gt: ["$score", {$multiply: ["$age", 4]}]}
})
// 张三(20×4=80<91) / 李四(19×4=76<92) / 赵六(20×4=80<96.8) / 周八(19×4=76<85) / 郑十(21×4=84<99)
8.3 数组高级查询
javascript
// $elemMatch:数组元素满足复杂条件
db.students.find({
scores: {$elemMatch: {$gte: 80, $lt: 90}}
})
// $all:数组同时包含所有元素
db.students.find({hobbies: {$all: ["编程", "数学"]}})
// $size:数组长度精确匹配
db.students.find({hobbies: {$size: 1}})
// 王五 ["游泳"] / 孙七 ["游戏"] / 吴九 ["足球"]
8.4 findOneAndXxx ------ 原子操作
javascript
// findOneAndUpdate:查询并更新,返回文档
db.students.findOneAndUpdate(
{name: "张三"},
{$set: {remark: "优秀班长"}},
{returnDocument: "after", projection: {_id:0, name:1, remark:1}}
)
// { name: '张三', remark: '优秀班长' }
// findOneAndDelete:查询并删除,返回被删文档
db.students.findOneAndDelete({name: "临时测试"})
// findOneAndReplace:查询并替换
db.students.findOneAndReplace(
{name: "xxx"},
{name: "xxx", score: 0},
{upsert: true} // 不存在则插入
)
8.5 upsert ------ 存在则更新,不存在则插入
javascript
db.students.updateOne(
{name: "新同学"},
{$set: {gender: "M", age: 18, score: 72.0}},
{upsert: true}
)
// 找不到 name="新同学" 时自动 insert
九、完整实验:成绩管理系统
整合以上知识,构建一个完整的成绩管理数据库:
javascript
// 使用 school 数据库
use school
// 课程集合
db.createCollection("course_info")
db.course_info.insertMany([
{name: "高等数学", credit: 4, teacher: "刘老师"},
{name: "英语", credit: 3, teacher: "陈老师"},
{name: "数据库原理", credit: 3, teacher: "张老师"},
{name: "操作系统", credit: 3, teacher: "王老师"}
])
// 成绩集合(每条记录含学生名+课程名,模拟非范式化设计)
db.createCollection("scores")
db.scores.insertMany([
{student: "张三", course: "高等数学", score: 82, examDate: new Date("2026-01-10")},
{student: "张三", course: "英语", score: 79, examDate: new Date("2026-01-11")},
{student: "郑十", course: "高等数学", score: 99, examDate: new Date("2026-01-10")},
// ...
])
// 统计每门课程平均分
db.scores.aggregate([
{$group: {_id: "$course", avgScore: {$avg: "$score"}, count: {$sum: 1}}},
{$sort: {avgScore: -1}}
])
// 统计每位学生的平均分排名
db.scores.aggregate([
{$group: {_id: "$student", avg: {$avg: "$score"}, total: {$sum: "$score"}}},
{$sort: {avg: -1}},
{$project: {_id:0, name:"$_id", avg:{$round:["$avg",2]}, total:1}}
])
十、常用命令速查
Shell 命令
| 命令 | 说明 |
|---|---|
show dbs |
列出所有数据库 |
use <db> |
切换数据库 |
show collections |
列出当前库集合 |
db.stats() |
数据库统计 |
db.<coll>.stats() |
集合统计 |
exit / Ctrl+D |
退出 shell |
常用查询链式调用
javascript
db.collection
.find(filter, projection)
.sort({field: 1}) // 1 升序,-1 降序
.skip(n)
.limit(n)
.toArray()
操作符速查
比较:$eq $ne $gt $gte $lt $lte $in $nin
逻辑:$and $or $nor $not
元素:$exists $type
数组:$size $all $elemMatch $push $pull $addToSet
更新:$set $unset $inc $rename $push $pull
聚合:$match $group $sort $limit $skip $project $unwind $lookup
总结
| 章节 | 核心知识点 |
|---|---|
| MongoDB 概念 | 文档数据库、BSON 类型、与 SQL 对比 |
| 安装 | apt 源安装、systemctl 管理、mongosh 连接 |
| 数据库/集合 | createCollection、drop、rename、stats |
| 文档 CRUD | insertOne/Many、find、updateOne/Many、deleteOne/Many |
| 查询语法 | 比较符、逻辑符、嵌套查询、数组查询、正则、$expr |
| 索引 | 单字段、复合、唯一、文本、TTL、2dsphere、explain |
| 聚合管道 | match/match/match/group/sort/sort/sort/project/unwind/unwind/unwind/lookup |
| 高级查询 | nor/nor/nor/not/size/size/size/elemMatch、findOneAndUpdate、upsert |
实操集群:ecs-ff10-0002 · Ubuntu 24.04.4 · MongoDB 8.0.26 · 2026-06-20