MongoDB 与 NoSQL
在全栈开发(尤其是 MERN/MEAN 技术栈)中,Node.js 与 MongoDB 是经典的黄金搭档。MongoDB 是一个基于分布式文件存储的 NoSQL 数据库。
核心概念对比
| 关系型数据库 (MySQL) | MongoDB (文档型) | 说明 |
|---|---|---|
| Database (数据库) | Database (数据库) | 数据库 |
| Table (表) | Collection (集合) | 数据表/集合 |
| Row (行) | Document (文档) | 数据记录,MongoDB 中是 BSON(类似 JSON)格式 |
| Column (列) | Field (字段) | 数据字段 |
| JOIN (连接) | $lookup / Populate | 关联查询 |
为什么 Node.js 常配 MongoDB?
面试题:为什么 Node.js 经常搭配 MongoDB 而不是 MySQL?
- 数据结构契合:MongoDB 存储的是 BSON(Binary JSON),在 Node.js 中操作数据就像直接操作 JS 原生对象一样,无需经过复杂的 ORM 映射。
- 无模式(Schema-less)灵活性:早期创业或敏捷开发阶段数据结构变化快,MongoDB 集合不需要预先定义字段和类型,加字段直接存即可。
- 高并发读写与扩展性:自带分片(Sharding)和副本集(Replica Set),水平扩展能力强,适合处理海量数据、日志、爬虫数据等。
注意:现在大型 Node.js 项目(如 NestJS)配合 MySQL / PostgreSQL 也非常普遍,具体取决于业务场景(是否强事务、复杂多表关联)。
Mongoose 核心机制
在 Node.js 中,最常用的 MongoDB 对象模型工具是 Mongoose。
Schema 与 Model
- Schema (模式):定义集合中文档的结构、字段类型、默认值、验证规则。
- Model (模型):由 Schema 编译而来,代表数据库中的一个集合,提供增删改查等静态方法。
const mongoose = require('mongoose');
// 1. 定义 Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, min: 18 },
email: { type: String, unique: true }, // 自动创建唯一索引
createdAt: { type: Date, default: Date.now }
});
// 2. 编译为 Model
const User = mongoose.model('User', userSchema);
// 3. 增删改查
const newUser = await User.create({ name: 'Alice', age: 25, email: 'alice@test.com' });
const users = await User.find({ age: { $gte: 20 } }).sort({ createdAt: -1 });
关联查询 (Populate)
MongoDB 默认不支持 JOIN,但 Mongoose 提供了 populate 来模拟关联查询(在业务层做了二次查询):
const postSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } // 关联 User
});
const Post = mongoose.model('Post', postSchema);
// 查询文章并自动填充作者信息
const posts = await Post.find().populate('author', 'name email');
// 结果中 author 会被替换为 { _id: ..., name: '...', email: '...' }
MongoDB 聚合管道 (Aggregation Pipeline)
面试题:如何在 MongoDB 中做复杂的数据统计?
使用聚合管道。它将多个处理阶段组合在一起,数据像流水线一样经过一个个阶段(Stage)进行过滤、分组、排序。
常用聚合操作符:
$match:过滤数据(类似 SQL 的WHERE)。$group:分组统计(类似 SQL 的GROUP BY),常用$sum、$avg。$project:选取/重命名字段(类似 SQL 的SELECT)。$sort:排序。$limit/$skip:分页。$lookup:原生的左外连接(类似 SQL 的LEFT JOIN)。
// 统计每个年龄段的用户数,且只统计 18 岁以上的
const stats = await User.aggregate([
{ $match: { age: { $gte: 18 } } },
{ $group: { _id: "$age", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]);
索引与性能优化
- 单字段索引:
{ age: 1 }(1 为升序,-1 为降序)。 - 复合索引:
{ age: 1, createdAt: -1 }。同样遵循最左前缀原则。 - 唯一索引:保证字段唯一性,如账号、邮箱。
- TTL 索引:设置过期时间,MongoDB 后台会自动删除过期数据(常用于日志、验证码、Session)。
- 空间索引 (2d, 2dsphere):用于查找附近的人、地理位置范围查询(LBS 应用必考)。
执行计划分析: 和 MySQL 的
EXPLAIN类似,MongoDB 使用cursor.explain("executionStats")来分析查询性能。重点看:
COLLSCAN:全表扫描(最差)。IXSCAN:索引扫描(理想)。FETCH:通过索引获取原文档。
事务支持
面试题:MongoDB 支持事务吗?
早期版本不支持多文档事务。自 MongoDB 4.0起,开始支持副本集的多文档 ACID 事务;4.2起支持分片集群事务。但在实际开发中,依然建议通过合理的 Schema 设计(嵌套文档)来尽量避免使用分布式事务,以保证性能。