MongoDB 学习笔记
引言
MongoDB 是一种非关系型数据库(NoSQL, Not only SQL),文档模型比传统表结构松,字段可以按文档变化。
它和关系库仍能大致对上:
| RDBMS | MongoDB |
|---|---|
| 库 (database) | 数据库 (database) |
| 表 (table) | 集合 (collection) |
| 行 (row) | 文档 (document) |
| 列 (column) | 字段 (field) |
| 主键 (primary key) | _id(默认自动生成) |
文档是 BSON(Binary JSON)。_id 不指定时会生成 ObjectId。
下面命令在 mongosh(或旧版 mongo shell)里用。collection 都换成真实集合名。
库与集合
1show dbs
2// 等价
3show databases
4
5use demo // 没有这个库会在写入时创建
6db // 当前库
7show collections
8
9db.createCollection("user")
10db.user.drop() // 删集合
11db.dropDatabase() // 删当前库,慎用
插入
insert() 还能用,但新版本推荐 insertOne / insertMany,返回值更清楚。
1db.user.insertOne({ name: "jack", sex: "male", age: 18 })
2
3db.user.insertMany([
4 { name: "tom", sex: "male", age: 19 },
5 { name: "marry", sex: "female", age: 20 }
6])
查询
1db.user.find() // 全部
2db.user.find({ name: "jack" }) // 等值
3db.user.find({ age: { $gte: 18, $lt: 30 } })
4db.user.find({ name: { $in: ["jack", "tom"] } })
5db.user.find({ age: { $exists: true } })
6
7db.user.findOne({ name: "jack" })
8
9// 只返回部分字段:1 要,0 不要。_id 默认会带,不想要就显式关掉
10db.user.find({ age: { $gte: 18 } }, { name: 1, age: 1, _id: 0 })
11
12db.user.find().sort({ age: -1 }).skip(10).limit(10)
13db.user.countDocuments({ sex: "male" })
常用操作符:$eq $ne $gt $gte $lt $lte $in $nin $and $or $exists $regex。
1db.user.find({
2 $or: [
3 { age: { $gte: 20 } },
4 { name: /^j/ }
5 ]
6})
更新
1// 只改匹配到的第一篇
2db.user.updateOne(
3 { name: "jack" },
4 { $set: { age: 19 }, $currentDate: { updatedAt: true } }
5)
6
7// 匹配到的全部改
8db.user.updateMany(
9 { sex: "male" },
10 { $inc: { age: 1 } }
11)
12
13// 没有就插入(upsert)
14db.user.updateOne(
15 { name: "lily" },
16 { $set: { sex: "female", age: 21 } },
17 { upsert: true }
18)
19
20// 整篇替换,慎用,会丢掉没写出来的字段
21db.user.replaceOne({ name: "tom" }, { name: "tom", age: 22 })
常用更新符:$set $unset $inc $push $pull $addToSet。不要用「查询文档」当更新文档,否则容易把整篇覆盖掉。
删除
1db.user.deleteOne({ name: "jack" })
2db.user.deleteMany({ age: { $lt: 18 } })
deleteMany({}) 会清空集合,相当于 TRUNCATE,先确认过滤条件。
索引
没有索引的字段上做等值、排序、范围查询,集合一大就会全表扫。
1db.user.createIndex({ name: 1 }) // 1 升序,-1 降序
2db.user.createIndex({ sex: 1, age: -1 }) // 复合索引
3db.user.createIndex({ name: 1 }, { unique: true })
4
5db.user.getIndexes()
6db.user.dropIndex("name_1")
7
8// 看这条查询走了什么计划
9db.user.find({ name: "jack" }).explain("executionStats")
复合索引遵循最左前缀:{ sex: 1, age: -1 } 能撑起 sex,以及 sex + age,撑不起只查 age。
聚合(够用的最小集)
1db.user.aggregate([
2 { $match: { age: { $gte: 18 } } },
3 { $group: { _id: "$sex", avgAge: { $avg: "$age" }, n: { $sum: 1 } } },
4 { $sort: { n: -1 } }
5])
管道按阶段走,常用:$match $project $group $sort $limit $lookup。过滤尽量放前面,少扫文档。
和 MySQL 对照时容易踩的坑
- 集合没有强制 schema,写错字段名不会报错,只会默默多一个字段。
- 单文档 16MB 上限,别把大文件塞进文档,用 GridFS 或对象存储。
- 默认读关心点是够用的,涉及资金、库存这类要看事务和写关注(
writeConcern)。4.4 之后副本集上的多文档事务才比较实用。 find()返回的是游标,shell 会自动迭代前 20 条,程序里要自己toArray()或遍历。