Skip to main content

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 设计(嵌套文档)来尽量避免使用分布式事务,以保证性能。