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人有坐骑,济公是降龙转世
扒一扒阿怡代打事件始末 牺牲品还是诈骗犯