MongoDB 入门详解

MongoDB 入门详解

前言在构建现代 Web 应用时,我们经常需要在关系型数据库(MySQL、PostgreSQL)和文档型数据库(MongoDB)之间做选择。MongoDB 以其灵活的文档模型、强大的查询能力和水平扩展特性,成为许多开发者的首选。本篇文章将带你从零开始掌握 MongoDB 的核心用法。

一、MongoDB 基础概念1.1 解决了什么问题?

关系型数据库需要预定义表结构,字段变更成本高。

某些业务场景(如用户画像、内容管理)需要灵活的数据结构,不同文档可能有不同字段。

MongoDB 采用 BSON(Binary JSON) 格式存储文档,无需固定 schema,支持嵌套对象和数组。

1.2 核心概念对比

概念

关系型数据库

MongoDB

数据库

Database

Database

Table

Collection(集合)

Row

Document(文档)

Column

Field(字段)

主键

Primary Key

_id(自动生成)

Collection(集合):类似于表,但无需预定义结构。

Document(文档):类似于行,但可以包含嵌套对象和数组。

Field(字段):文档中的键值对,支持多种数据类型。

二、安装与连接2.1 本地安装macOS(使用 Homebrew):

brew tap mongodb/brew

brew install mongodb-community

brew services start mongodb-communityDocker 方式(推荐):

docker run -d -p 27017:27017 --name mongodb mongo:latest2.2 连接字符串格式mongodb://[username:password@]host[:port][/database][?options]示例:

mongodb://localhost:27017/myapp

mongodb://user:pass@localhost:27017/myapp?authSource=admin

mongodb+srv://user:pass@cluster.mongodb.net/myapp

默认端口:27017

无认证时可直接连接本地实例

MongoDB Atlas(云服务)使用 mongodb+srv:// 协议

三、Node.js 连接实战使用 mongoose 库(最流行的 MongoDB ODM):

import mongoose from 'mongoose';

// 连接 MongoDB

const connectDB = async () => {

try {

await mongoose.connect('mongodb://localhost:27017/myapp', {

useNewUrlParser: true,

useUnifiedTopology: true,

});

console.log('MongoDB 连接成功');

} catch (error) {

console.error('连接失败:', error);

process.exit(1);

}

};

connectDB();

// 定义 Schema(数据模型)

const userSchema = new mongoose.Schema({

name: { type: String, required: true },

email: { type: String, unique: true, required: true },

age: { type: Number, min: 0 },

tags: [String],

createdAt: { type: Date, default: Date.now },

});

// 创建 Model

const User = mongoose.model('User', userSchema);

mongoose.connect() 返回 Promise,支持 async/await

Schema 定义字段类型和验证规则

Model 提供数据库操作方法

四、CRUD 操作详解4.1 创建(Create)// 单条插入

const newUser = await User.create({

name: '张三',

email: 'zhangsan@example.com',

age: 25,

tags: ['前端', 'Node.js'],

});

// 或使用 save()

const user = new User({ name: '李四', email: 'lisi@example.com' });

await user.save();

// 批量插入

await User.insertMany([

{ name: '王五', email: 'wangwu@example.com' },

{ name: '赵六', email: 'zhaoliu@example.com' },

]);4.2 查询(Read)// 查询所有

const users = await User.find();

// 条件查询

const user = await User.findOne({ email: 'zhangsan@example.com' });

const adults = await User.find({ age: { $gte: 18 } });

// 投影(只返回指定字段)

const names = await User.find({}, 'name email');

// 排序与分页

const users = await User.find()

.sort({ createdAt: -1 })

.skip(10)

.limit(20);

// 复杂查询

const result = await User.find({

age: { $gte: 18, $lte: 65 },

tags: { $in: ['前端', '后端'] },

name: { $regex: /张/, $options: 'i' },

});常用查询操作符:

$gt、$gte、$lt、$lte:比较

$in、$nin:包含/不包含

$regex:正则匹配

$exists:字段存在性

$or、$and:逻辑运算

4.3 更新(Update)// 更新单条(返回更新后的文档)

const updated = await User.findOneAndUpdate(

{ email: 'zhangsan@example.com' },

{ age: 26 },

{ new: true } // 返回更新后的文档

);

// 更新多条

await User.updateMany(

{ age: { $lt: 18 } },

{ $set: { status: 'minor' } }

);

// 使用操作符

await User.updateOne(

{ _id: userId },

{

$inc: { age: 1 }, // 自增

$push: { tags: '新标签' }, // 数组追加

$set: { updatedAt: Date.now() }

}

);常用更新操作符:

$set:设置字段值

$unset:删除字段

$inc:数值增减

$push、$pull:数组操作

$addToSet:数组去重追加

4.4 删除(Delete)// 删除单条

await User.deleteOne({ email: 'zhangsan@example.com' });

// 删除多条

await User.deleteMany({ age: { $lt: 18 } });

// 查找并删除(返回被删除的文档)

const deleted = await User.findOneAndDelete({ _id: userId });五、聚合查询(Aggregation)聚合管道是 MongoDB 最强大的功能之一,类似 SQL 的 GROUP BY:

// 统计各年龄段用户数量

const stats = await User.aggregate([

{ $match: { age: { $exists: true } } }, // 过滤

{ $group: {

_id: { $switch: {

branches: [

{ case: { $lt: ['$age', 18] }, then: '未成年' },

{ case: { $lt: ['$age', 30] }, then: '青年' },

{ case: { $lt: ['$age', 50] }, then: '中年' },

{ case: true, then: '老年' }

],

default: '未知'

}},

count: { $sum: 1 },

avgAge: { $avg: '$age' }

}},

{ $sort: { count: -1 } }

]);

// 查找包含特定标签的用户,并展开标签数组

const result = await User.aggregate([

{ $match: { tags: { $exists: true, $ne: [] } } },

{ $unwind: '$tags' },

{ $group: {

_id: '$tags',

users: { $push: '$name' }

}}

]);常用聚合阶段:

$match:过滤文档

$group:分组统计

$project:字段投影

$sort:排序

$limit、$skip:分页

$unwind:展开数组

$lookup:关联查询(类似 JOIN)

六、索引优化索引能大幅提升查询性能:

// 创建单字段索引

await User.createIndex({ email: 1 }); // 1 升序,-1 降序

// 创建复合索引

await User.createIndex({ name: 1, age: -1 });

// 创建文本索引(支持全文搜索)

await User.createIndex({ name: 'text', bio: 'text' });

// 查看索引

const indexes = await User.collection.getIndexes();

// 删除索引

await User.collection.dropIndex('email_1');索引策略建议:

为频繁查询的字段创建索引

为唯一字段(如 email)创建唯一索引

复合索引遵循"最左前缀"原则

避免过多索引,影响写入性能

使用 explain() 分析查询计划

// 分析查询性能

const explain = await User.find({ email: 'test@example.com' }).explain();

console.log(explain.executionStats);七、实战示例:博客文章系统结合《博客搭建二-文章接口》,用 MongoDB 实现文章管理:

import mongoose from 'mongoose';

// 文章 Schema

const articleSchema = new mongoose.Schema({

title: { type: String, required: true, index: true },

content: { type: String, required: true },

author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },

tags: [String],

views: { type: Number, default: 0 },

published: { type: Boolean, default: false },

publishedAt: Date,

createdAt: { type: Date, default: Date.now },

updatedAt: { type: Date, default: Date.now },

});

// 自动更新 updatedAt

articleSchema.pre('save', function(next) {

this.updatedAt = Date.now();

next();

});

const Article = mongoose.model('Article', articleSchema);

// 创建文章

const createArticle = async (data) => {

return await Article.create({

...data,

publishedAt: data.published ? Date.now() : null,

});

};

// 查询已发布文章(带作者信息)

const getPublishedArticles = async (page = 1, limit = 10) => {

return await Article.find({ published: true })

.populate('author', 'name email') // 关联查询用户信息

.sort({ publishedAt: -1 })

.skip((page - 1) * limit)

.limit(limit);

};

// 增加浏览量

const incrementViews = async (articleId) => {

return await Article.findByIdAndUpdate(

articleId,

{ $inc: { views: 1 } },

{ new: true }

);

};

// 按标签统计文章数

const getTagStats = async () => {

return await Article.aggregate([

{ $match: { published: true } },

{ $unwind: '$tags' },

{ $group: {

_id: '$tags',

count: { $sum: 1 },

articles: { $push: { title: '$title', id: '$_id' } }

}},

{ $sort: { count: -1 } }

]);

};八、性能优化与最佳实践

连接池管理

mongoose.connect(uri, {

maxPoolSize: 10, // 最大连接数

minPoolSize: 5, // 最小连接数

serverSelectionTimeoutMS: 5000,

});

批量操作优化

// 使用 bulkWrite 替代多次 updateOne

await User.bulkWrite([

{ updateOne: { filter: { _id: id1 }, update: { $set: { status: 'active' } } } },

{ updateOne: { filter: { _id: id2 }, update: { $set: { status: 'inactive' } } } },

]);

查询优化

使用 select() 只查询需要的字段

避免 $regex 在大量数据上的全表扫描

合理使用 lean() 返回纯 JavaScript 对象(不创建 Mongoose 文档实例)

const users = await User.find().lean(); // 性能更好,但失去 Mongoose 方法

数据验证const userSchema = new mongoose.Schema({

email: {

type: String,

required: true,

unique: true,

validate: {

validator: (v) => /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(v),

message: '邮箱格式不正确'

}

}

});

九、常见排障清单

MongoServerError: E11000 duplicate key error

检查唯一索引字段是否有重复值,或删除冲突的索引后重建。

MongooseError: Operationusers.findOne()buffering timed out after 10000ms

确认 MongoDB 服务已启动,检查连接字符串是否正确。

查询性能慢

使用 explain() 分析查询计划,检查是否使用了索引。

考虑添加复合索引或优化查询条件。

CastError: Cast to ObjectId failed

确保传入的 _id 是有效的 MongoDB ObjectId 格式(24 位十六进制字符串)。

内存占用过高

使用 lean() 查询,避免创建大量 Mongoose 文档实例。

限制查询结果数量,使用分页。

总结

MongoDB 是文档型数据库,适合灵活的数据结构和快速迭代。

Schema 定义提供数据验证,但 MongoDB 本身支持无结构存储。

索引是性能优化的关键,合理使用单字段、复合和文本索引。

聚合管道功能强大,能处理复杂的统计和分析需求。

在 Node.js 中使用 mongoose 可以简化操作,但要注意连接池和查询优化。

生产环境建议使用 MongoDB Atlas 或配置副本集,保证高可用性。

掌握 MongoDB 的核心用法和优化技巧,你就能在项目中灵活选择数据存储方案,构建高性能、可扩展的 Web 应用。

原文地址:https://webfem.com/post/mongodb-usage-guide,转载请注明出处

养生小贴士

青蛙吃什么,主要以昆虫为食
💡 小知识

青蛙吃什么,主要以昆虫为食

📅 09-25 👍 764
十八罗汉:都是什么来历?只有3人有坐骑,济公是降龙转世
扒一扒阿怡代打事件始末 牺牲品还是诈骗犯