啥是MongoDB?
- MongoDB 是一种 面向文档的 NoSQL 数据库。
啥是SQL?
- Structured Query Language
- 结构化查询语言
- 一种专门用于操作关系型数据库的编程语言
啥是NOSQL?
- Not Only SQL(不仅仅是SQL)
- 泛指所有不主要使用sql语言,不严格使用表格结构的数据库
MongoDB,MySQL和Redis的区别是什么?
| 维度 | MySQL | MongoDB | Redis |
|---|---|---|---|
| 数据库类型 | 关系型数据库 | 文档型 NoSQL 数据库 | 键值对型 NoSQL 数据库(内存数据库) |
| 设计哲学 | 绝对-数据的一致性 | 变捷-数据的灵活性 | 极速-数据的极致性能 |
| 数据存储位置 | 磁盘(主)+ 内存缓存 | 磁盘(主)+ 内存映射 | 内存(主)+ 磁盘(持久化备份) |
| 存储格式 | 面向表格 | 面向文档 | 面向键值对(面向数据的结构) |
| 存储架构 | 面向磁盘的页式存储系统 | 面向内存映射文件的文档存储系统 | 面向内存的数据结构服务器 |
MongoDB的存储结构是怎样的?如何理解他们?
存储结构:
css
MongoDB 实例
├── 数据库(Database)
│ └── 集合(Collection)------ 类似MySQL的表,但无Schema约束
│ └── 文档(Document)------ JSON/BSON格式,可嵌套
│ ├── 字段1: 值
│ ├── 字段2: 数组 [值1, 值2]
│ └── 嵌套文档: {子字段1, 子字段2}
如何理解五层存储层级?
1.类比JS的对象
- 一个数据库是一个巨大的对象 :你连接 MongoDB 时,拿到的
db对象。 - 一个集合是对象数组:`db.collection 就相当于一个数组,里面放了无数个对象。
- 一个文档是数组里的一个对象 :
{ field1: "...", field2: "...", }就是数组里的具体对象。 - 一个字段是对象的属性 :
doc.field1、doc.field2。 (严谨地讲)有两处微小的物理差异:
MongoDB 服务器在物理存储(磁盘层面) 和 JavaScript 数组有两点不同:
-
差异一:数组有顺序,集合没有固定顺序
- JS 数组是
[0号, 1号, 2号],有严格的下标。 - MongoDB 集合里的文档是"无序"的(除非你特意用
{ $sort }排序)。你不能说"把第 0 个文档拿出来",你只能说"把_id为 xxx 的文档拿出来"。
- JS 数组是
-
差异二:数组在内存里,集合在磁盘里
- JS 数组是一次性全部加载到内存里的。
- MongoDB 的集合可能大到几百个 GB,它是一点一点从磁盘读到内存(通过游标)给你的,不会一口气全部倒出来。
2.类比Mysql的存储结构
整体层次对比
| 层次 | MongoDB | MySQL (InnoDB) |
|---|---|---|
| 第一层 | 实例(一个 mongod 进程) | 实例(一个 mysqld 进程) |
| 第二层 | 数据库(Database) | 数据库(Schema / Database) |
| 第三层 | 集合(Collection) | 表(Table) |
| 第四层 | 文档(Document) | 行(Row) |
| 第五层 | 字段(Field) | 列(Column) |
对应关系:
- MongoDB 的 集合 → MySQL 的 表
- MongoDB 的 文档 → MySQL 的 行
- MongoDB 的 字段 → MySQL 的 列
最大区别:
- MongoDB:同一个集合里的不同文档,可以有完全不同的字段
- MySQL:同一个表里的所有行,必须有完全相同的列结构(预定义的,结构化的)
啥是BSON?
BSON 是 Binary JSON 的缩写,是一种二进制编码的序列化格式,主要用于 MongoDB 的数据存储和网络传输。
简单说:BSON = JSON 的二进制版本。
Moongodb数据库的基本CRUD操作:
一.准备工作
1. 启动 MongoDB 的命令行
mongosh
2. 查看所有数据库
sql
show dbs
3. 切换到指定的数据库(存在则切换,不存在则创建)
rust
use myApp//切换或创建一个名为myApp的数据库
4. 查看当前数据库的所有集合
sql
show collections
二、C - Create(创建文档)
给某个集合插入单条文档
javascript
db.collection1.insertOne({//collection1是你给集合的命名,内容任意
title: "后悔大学没好好学英语",
content: "现在看外文文档非常吃力",
candles: 5,
position: { x: 30, y: 50 }
})
返回结果:
javascript
{
acknowledged: true,//创建成功
insertedId: ObjectId("65d8a1b2c3d4e5f6g7h8i9j0") // MongoDB 自动生成的 _id
}
给某个集合插入多条文档
javascript
db.collection2.insertMany([
{
title: "后悔没去留学",
content: "当年有机会却没去,现在工作后没时间了",
candles: 5,
position: { x: 20, y: 60 }
},
{
title: "后悔没早点学编程",
content: "28岁才转行,要是早几年就好了",
candles: 12,
position: { x: 80, y: 40 }
}
])
三、R - Read(读取文档)
1. 查询某个集合的所有文档
javascript
db.collection1.find({})
返回一个游标(可以理解为"结果集"),里面是所有文档:
yaml
[
{
_id: ObjectId('6a312d7717f717958fabc114'),
title: '后悔大学没好好学英语',
content: '现在看外文文档非常吃力',
candles: 5,
position: { x: 30, y: 50 }
},
{
.......
}
]
美化输出(让显示更整齐):
javascript
db.regrets.find({}).pretty()
2. 按条件查询
javascript
// 查询标题为"后悔没学好英语"的文档
db.collection1.find({ title: "后悔没学好英语" })
// 查询 candles 大于 5 的文档
db.collection1.find({ candles: { $gt: 5 } })
// 查询 candles 在 3 到 10 之间的文档
db.collection2.find({ candles: { $gte: 3, $lte: 10 } })
3. 查询单条文档
javascript
// 按 _id 查(最常用)
db.collection2.findOne({ _id: ObjectId("65d8a1b2c3d4e5f6g7h8i9j0") })
4. 条件组合查询
javascript
// 查询 candles 大于 3,且 position.x 小于 50 的文档
db.collection2.find({
candles: { $gt: 3 },
"position.x": { $lt: 50 } // 注意嵌套字段要用引号包起来
})
5. 常见的查询操作符
| 操作符 | 含义 | 例子 |
|---|---|---|
$eq |
等于 | { candles: { $eq: 5 } } |
$gt |
大于 | { candles: { $gt: 5 } } |
$gte |
大于等于 | { candles: { $gte: 5 } } |
$lt |
小于 | { candles: { $lt: 5 } } |
$lte |
小于等于 | { candles: { $lte: 5 } } |
$ne |
不等于 | { candles: { $ne: 5 } } |
$in |
在数组中 | { title: { $in: ["后悔A", "后悔B"] } } |
$and |
逻辑与 | { $and: [{ candles: { $gt: 3 } }, { candles: { $lt: 10 } }] } |
$or |
逻辑或 | { $or: [{ title: "后悔A" }, { title: "后悔B" }] } |
6. 投影(只返回指定字段)
javascript
// 只返回 title 和 candles,不返回 content
db.collection2.find({}, { title: 1, candles: 1, _id: 0 })
// 1 表示返回,0 表示不返回
7. 排序、限制、跳过
javascript
// 按 candles 升序排序(1 升序,-1 降序)
db.collection2.find({}).sort({ candles: 1 })
// 只返回前 5 条
db.collection2.find({}).limit(5)
// 跳过前 10 条(分页用)
db.collection2.find({}).skip(10)
// 组合使用:按蜡烛数降序,取第 11 到 20 条
db.collection2.find({}).sort({ candles: -1 }).skip(10).limit(10)
四、U - Update(更新文档)
1. 更新单条文档
javascript
// 找到 candles 为 0 的第一条,把 candles 改成 1
db.collection2.updateOne(
{ candles: 0 }, // 查询条件
{ $set: { candles: 1 } } // 更新操作
)
2. 更新多条文档
javascript
// 把所有 candles 为 0 的文档,都改成 1
db.collection2.updateMany(
{ candles: 0 },
{ $set: { candles: 1 } }
)
3. 常用更新操作符
| 操作符 | 含义 | 例子 |
|---|---|---|
$set |
设置字段值 | { $set: { title: "新标题" } } |
$inc |
数字增减 | { $inc: { candles: 1 } } (蜡烛数 +1) |
$unset |
删除字段 | { $unset: { position: "" } } |
$push |
往数组里加元素 | { $push: { tags: "人生" } } |
$pull |
从数组里移除元素 | { $pull: { tags: "人生" } } |
4. 更新并返回更新后的文档
javascript
// findOneAndUpdate:找到并更新,返回更新后的文档
db.collection2.findOneAndUpdate(
{ title: "后悔没学好英语" },
{ $inc: { candles: 1 } },
{ returnDocument: "after" } // "after" 返回更新后的,"before" 返回更新前的
)
5. 替换整个文档(不推荐,一般用 $set)
javascript
// 把整个文档替换成新的内容(危险操作,会删除原有字段)
db.collection2.replaceOne(
{ title: "后悔没学好英语" },
{ title: "新标题", content: "新内容" } // 旧文档里的 candles、position 都没了
)
五、D - Delete(删除文档)
1. 删除单条文档
javascript
// 删除标题为"后悔没学好英语"的第一条
db.collection2.deleteOne({ title: "后悔没学好英语" })
// 按 _id 删除
db.collection2.deleteOne({ _id: ObjectId("65d8a1b2c3d4e5f6g7h8i9j0") })
2. 删除多条文档
javascript
// 删除所有 candles 为 0 的文档
db.collection2.deleteMany({ candles: 0 })
// 删除集合里的所有文档(危险!)
db.collection2.deleteMany({})
3. 删除并返回被删除的文档
javascript
// findOneAndDelete:找到并删除,返回被删除的文档
db.collection2.findOneAndDelete({ title: "后悔没学好英语" })
什么是mongoose?
Mongoose 是一个 Node.js 的 MongoDB ODM(对象文档映射)库,用于在 Node.js 应用中操作 MongoDB 数据库。
说人话就是一个允许你在 Node.js 应用中操作 MongoDB 数据库的高级客户端。
mongoose操作mongoDB数据库的基本语法:
1. 定义 Schema 与 Model(模型)
javascript
import mongoose from "mongoose";
// 1. 定义 Schema(数据结构蓝图)
const collection2Schema = new mongoose.Schema(
{
// 字段定义区
title: {
type: String,//字段数据类型
required: true,//插入文档时是否必须存在该字段,
//底层会检查 `title` 是否为 `null` 或 `undefined`,如果是则抛出 `ValidationError`。
trim: true,//插入文档时是否在存库前自动去掉字符串首尾的空格。
maxlength: 40、、验证字符串长度不能超过 40。超过则报错。
},
content: {
type: String,
required: true,
trim: true,
maxlength: 1000
},
candles: {
type: Number,
default: 0,//插入文档时,如果没提供 `candles` 字段,Mongoose 会自动填入 `0`。
//这是一个默认值,不由 MongoDB 处理,而是由 Mongoose 在入库前填充。
min: 0//设置字段最小值,如果尝试存入 `-5`,会触发验证错误。
},
//这里没有加 `required: true`,`position` 字段是可选的。插入时可以不传,或者只传 `x` 不传 `y`。
position: {
x: Number,
y: Number
},
// 引用文档示例(假设指向 User)
author: {
type: mongoose.Schema.Types.ObjectId,//存储的只是Id,不是数据本身
ref: "User"//这是**引用(Reference)** 的声明。
/*它告诉 Mongoose:"这个 `author` 字段存的 ID,指向的是 `User` 这个 Model(也就是 `users` 集合)里的某条文档"。*/
}
},
{
// 配置选项区
timestamps: { createdAt: true, updatedAt: false },
//控制插入文档时Mongoose 是否自动加 `createdAt`和`updateAt` 字段,
//creatAt值为文档第一次被插入数据库时记录的时间(一辈子只记录一次),
//updateAt值为文档每一次被修改并保存时,自动更新为"当前时间"(随时在变)。
versionKey: false
//Mongoose 默认会给每个文档加一个 `__v` 字段,每次修改文档并 `.save()` 时,`__v` 会自增 1
//解决冲突编辑风险
}
);
// 2. 导出 Model(注意:这里集合名会被命名为 "collection2")
export default mongoose.model("collection2", collection2Schema);
什么是Schema?它在mongoDB和Mysql两个不同的数据库上有什么不同?
- Schema是一种规范,它规定了集合(表)里的文档(行)应该有哪些字段、什么类型、是否必填等相关信息。
- Schema ≠ Database(数据库容器) 。
- 只是在 MySQL 里,
CREATE SCHEMA被当成了CREATE DATABASE的同义词,这才导致了混淆。
mongoDB语境下的Schema:
1. 字段定义区
- 基础类型 :
String、Number等,并附带验证(required、maxlength、min)。 - 嵌套文档 :
position: { x: Number, y: Number }------ 数据内嵌,逻辑上属于父文档。 - 引用文档 :
author: { type: ObjectId, ref: 'User' }------ 只存(ID),数据实际在别的集合。
2. 配置选项区
timestamps:告诉 Mongoose 自动维护时间戳(createdAt记录出生,updatedAt记录最近一次修改)。versionKey: false:控制是否记录乐观锁版本号(__v)
Schema 是挂载在 Collection(集合) 上,但检查的是 Document(文档) 下具体的每个 Field(字段) 。MongoDB 服务器本身只认
_id唯一性,其他规则全靠 Mongoose 在应用层拦截。
mysql语境下的Schema:
- 容器层:你可以在这里认为Schema = Database,因为
CREATE SCHEMA相当于CREATE DATABASE - 结构层:和mongoDB一样,是一种约束数据结构的规范。只不过在mysql里schema 是必须要有的。
什么是引用文档?和嵌套文档有什么区别?
文档里只存一个 "ObjectId ",真正的数据在另一个集合里。
arduino
// ========== 嵌套文档 ==========
// 位置信息完全写在遗憾文档内部
{
_id: "regret_001",
title: "后悔没学英语",
position: { // ← 这就是嵌套
x: 45.6,
y: 120.3
}
}
// ========== 引用文档 ==========
// 遗憾文档里只存作者的 ID
{
_id: "regret_002",
title: "后悔没告白",
author_id: "user_007" // ← 这就是引用(存的是别人的 ID),字段名自定义
}
// 作者的真实姓名、头像,存在另一个叫 users 的集合里
引用文档在Schema里的实例(假设指向 User)
rust
author: {
type: mongoose.Schema.Types.ObjectId,//存储的只是Id,不是数据本身
ref: "User"//这是**引用(Reference)** 的声明。
/*它告诉 Mongoose:"这个 `author` 字段存的 ID,指向的是 `User` 这个 Model(也就是 `users` 集合)里的某条文档"。*/
查询时,用 .populate() 自动填充:
csharp
// 业务代码
const regret = await Regret.findById('reg_001').populate('author');
console.log(regret.author);
// 打印出来的不再是 ObjectId("user_007"),而是:
// { _id: 'user_007', name: '张伟', avatar: 'avatar.jpg', email: '...' }之类的
const regret = await Regret.findById(req.params.id).populate('author', 'name avatar');
// 第二个参数表示:只取用户的 name 字段和 avatar字段,不要 email,节省流量!
到底怎么选?
"这个子数据,是永远只属于这一个父数据,还是会被很多父数据共用?"
-
如果"只属于一个" → 必须用嵌套文档。
- 例子:订单里的收货地址、购物车里的商品列表。地址不会同时属于两个订单,所以直接嵌进去。
-
如果"会被很多个共用" → 必须用引用文档。
- 例子:作者信息、商品分类、文章标签。一个作者会写几百篇文章,总不能把作者的名字复制几百遍吧?
2. 增(Create)
javascript
import Collection2 from "./models/collection2.js";
// 插入一条文档
const newDoc = await Collection2.create({
title: "后悔大学没好好学英语",
content: "现在看外文文档非常吃力",
candles: 5,
position: { x: 30, y: 50 }
});
// 批量插入多条文档(不常用,但语法如此)
const manyDocs = await Collection2.insertMany([
{ title: "后悔没早买房", content: "房价涨了三倍", candles: 100 },
{ title: "后悔没告白", content: "她成了别人的新娘", candles: 66 }
]);
3. 查(Read)
javascript
// 查询所有文档
const all = await Collection2.find();
// 精确条件查询(等值匹配)
const exact = await Collection2.find({ title: "后悔没告白" });
// 比较查询(大于 $gt)
const gt = await Collection2.find({ candles: { $gt: 50 } });
// 多条件"且"查询(嵌套字段用引号包裹)
const andQuery = await Collection2.find({
candles: { $gt: 10 },
"position.x": 30
});
// 多条件"或"查询($or)
const orQuery = await Collection2.find({
$or: [
{ title: "后悔没买房" },
{ candles: { $lt: 5 } }
]
});
// 只返回特定字段(投影):只要 title 和 candles,不要 _id
const projection = await Collection2.find(
{ candles: { $gt: 10 } },
{ title: 1, candles: 1, _id: 0 }
);
// 按 ObjectId 精确查找单条
const byId = await Collection2.findById("65f2a1b3c4d5e6f7a8b9c0d1");
// 查找单条(按条件)
const one = await Collection2.findOne({ title: "后悔没告白" });
// 排序 + 限制条数(按蜡烛数降序,取前3条)
const sorted = await Collection2.find()
.sort({ candles: -1 }) // -1 降序,1 升序
.limit(3);
// 统计文档数量(全部)
const totalCount = await Collection2.countDocuments();
// 统计符合条件的文档数量
const condCount = await Collection2.countDocuments({ candles: 50 });
// 【高级】关联查询(填充引用文档,即 Populate)
// 假设 author 字段存的是 User 的 _id
const withAuthor = await Collection2.find().populate("author");
// 这样拿到的结果里,author 不再是一个 ObjectId,而是完整的 User 对象。
4. 改(Update)
javascript
// 修改单个文档(局部更新,使用 $set)
await Collection2.updateOne(
{ title: "后悔没告白" }, // 条件
{ $set: { content: "她成了别人的新娘,新郎竟然是我哥们" } } // 修改内容
);
// 数字字段增加($inc,常用于点赞/蜡烛数)
await Collection2.updateOne(
{ title: "后悔没告白" },
{ $inc: { candles: 1 } } // 蜡烛数 +1
);
// 修改多个文档(批量更新)
await Collection2.updateMany(
{ candles: { $lt: 10 } }, // 条件:蜡烛少于10
{ $set: { status: "冷门遗憾" } } // 给这些文档加一个新字段 status
);
// 查找并更新(原子操作,常用)
// new: true 表示返回更新后的新文档,否则返回旧文档
const updated = await Collection2.findByIdAndUpdate(
"65f2a1b3c4d5e6f7a8b9c0d1", // ID
{ $set: { title: "新标题" } },
{ new: true }
);
5. 删(Delete)
javascript
// 删除单条文档(按条件)
await Collection2.deleteOne({ title: "后悔没早买房" });
// 删除单条文档(按 ID)
await Collection2.findByIdAndDelete("65f2a1b3c4d5e6f7a8b9c0d1");
// 删除多条文档(按条件)
await Collection2.deleteMany({ candles: { $lt: 0 } });
// 【高危】删除整个集合(慎用!)
// 在 Mongoose 里通常用原生方法
await Collection2.collection.drop();
6. 嵌套文档与引用的写法补充
-
定义嵌套文档(就像你的
position) :直接在 Schema 里写{ x: Number, y: Number }即可。 -
定义引用文档(关联其他集合) :
javascriptauthor: { type: mongoose.Schema.Types.ObjectId, ref: "User" // 这里 "User" 必须和你导出的 User 模型名完全一致 }
总结速查表(纯享版)
| 业务动作 | Mongoose 写法 (Collection2 模型) |
|---|---|
| 查全部 | Collection2.find() |
| 按条件查 | Collection2.find({ title: "..." }) |
| 按 ID 查 | Collection2.findById(id) |
| 查一条 | Collection2.findOne({ ... }) |
| 新增 | Collection2.create({ ... }) |
| 改一条 | Collection2.updateOne({条件}, {$set:{...}}) |
| 加数字 | Collection2.updateOne({条件}, {$inc:{candles:1}}) |
| 改多条 | Collection2.updateMany({条件}, {$set:{...}}) |
| 删一条 | Collection2.deleteOne({条件}) |
| 删多条 | Collection2.deleteMany({条件}) |
| 统计 | Collection2.countDocuments({条件}) |
| 排序/限制 | Collection2.find().sort({candles:-1}).limit(3) |
| 关联填充 | Collection2.find().populate("author") |
现在所有命令都集中在 Collection2 上了,你可以直接复制粘贴使用。如果还需要其他特定操作的写法(比如数组操作 $push、$pull),随时告诉我。
实际操作速查对比表(MongoDB Shell vs Mongoose)
| 操作 | MongoDB Shell | Mongoose (你的项目) |
|---|---|---|
| 插入单条 | db.collection2.insertOne({...}) |
db.Collection2.create({...}) |
| 插入多条 | db.collection2.insertMany([...]) |
await Collection2.insertMany([...]) |
| 查询所有 | db.collection2.find({}) |
await Collection2.find({}) |
| 按条件查询 | db.collection2.find({ candles: { $gt: 5 } }) |
await Collection2.find({ candles: { $gt: 5 } }) |
| 按 _id 查 | db.collection2.findOne({ _id: ObjectId("...") }) |
await Collection2.findById("...") |
| 更新单条 | db.collection2.updateOne({...}, { $set: {...} }) |
await Collection2.updateOne({...}, { $set: {...} }) |
| 更新并返回 | db.collection2.findOneAndUpdate({...}, {...}, { returnDocument: "after" }) |
await Collection2.findByIdAndUpdate("...", {...}, { new: true }) |
| 删除单条 | db.collection2.deleteOne({...}) |
await Collection2.deleteOne({...}) |
| 按 _id 删 | db.collection2.deleteOne({ _id: ObjectId("...") }) |
await Collection2.findByIdAndDelete("...") |
我也不是很懂的mongoDB底层:(我在胡说霸道)
列几个关键字就这样吧,哈哈哈哈哈哈
- mongoDB:os操作系统通过虚拟内存建立内存映射文件
- 工作集???