Liuqichun's Blog

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 对照时容易踩的坑

  1. 集合没有强制 schema,写错字段名不会报错,只会默默多一个字段。
  2. 单文档 16MB 上限,别把大文件塞进文档,用 GridFS 或对象存储。
  3. 默认读关心点是够用的,涉及资金、库存这类要看事务和写关注(writeConcern)。4.4 之后副本集上的多文档事务才比较实用。
  4. find() 返回的是游标,shell 会自动迭代前 20 条,程序里要自己 toArray() 或遍历。

官方手册:https://www.mongodb.com/docs/manual/

<< Previous Post

|

Next Post >>

#MongoDB