一、MongoDB简介:为什么选择文档数据库?
1.1 什么是MongoDB?
MongoDB是一个使用C++编写的文档数据库,其核心数据模型为JSON/BSON。它巧妙地位于关系型与非关系型数据库之间,被誉为“功能最丰富、最像关系数据库的NoSQL数据库”。
核心特点:
-
灵活的模式:集合中的文档无需拥有相同的字段,支持嵌套文档和数组
-
强大的查询语言:语法类似面向对象查询,几乎实现关系型数据库单表查询所有功能
-
完整的事务支持:支持ACID事务,满足企业级应用需求
-
高性能与高扩展:原生支持复制集和分片集群
根据DB-Engines 2021年12月排名,MongoDB在全部数据库中位列第5,在NoSQL数据库中排名第一,其社区活跃度和应用广度持续上升。
1.2 MongoDB vs 关系型数据库:概念映射
| 数据库(database) | 数据库(database) | 逻辑命名空间 |
| 集合(collection) | 表(table) | 文档的容器 |
| 文档(document) | 行(row) | 数据的基本单元 |
| 字段(field) | 列(column) | 文档中的属性 |
| _id字段 | 主键(primary key) | 每个文档的唯一标识 |
| 视图(view) | 视图(view) | 虚拟集合(3.4+版本支持) |
| $lookup聚合 | 表连接(table join) | 实现类似连接的功能 |
关键差异:
-
半结构化:无需预定义模式,字段可动态增减
-
弱关系:无外键约束,复杂关联通过聚合管道实现
1.3 技术优势与应用场景
四大核心优势:
开发高效:JSON结构与对象模型接近,代码量低
灵活响应:动态模型轻松适应业务需求变化
高可用:复制集提供99.999%可用性
无缝扩展:分片架构支持海量数据存储
六大应用场景:
游戏:存储用户信息、装备、积分(内嵌文档形式)
物流:订单状态跟踪(内嵌数组记录变更)
社交:用户信息、朋友圈、附近的人(地理位置索引)
物联网:设备信息、日志数据、多维度分析
视频直播:用户信息、礼物信息管理
大数据:云存储系统,实时数据提取分析
决策指南:只要满足以下任一条件,即可考虑使用MongoDB
-
需要快速迭代开发,数据模式频繁变化
-
数据具有嵌套的多层次结构
-
需要处理海量数据和高并发访问
-
追求高可用性和水平扩展能力
-
开发团队熟悉JavaScript/JSON技术栈
二、环境搭建:多平台部署指南
2.1 Linux系统安装(CentOS 7为例)
1. 下载与解压
bash
# 下载MongoDB 6.0.5
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-6.0.5.tgz
# 解压
tar -zxvf mongodb-linux-x86_64-rhel70-6.0.5.tgz
2. 目录准备与启动
bash
# 创建数据目录和日志目录
mkdir -p /mongodb/data /mongodb/log
# 启动服务(后台运行)
cd mongodb
bin/mongod –port=27017 –dbpath=/mongodb/data \\
–logpath=/mongodb/log/mongodb.log \\
–bind_ip=0.0.0.0 –fork
关键启动参数:
-
–dbpath:数据文件存储目录
-
–logpath:日志文件路径(必须是文件,不是目录)
-
–bind_ip:绑定IP(0.0.0.0表示监听所有网卡)
-
–fork:后台运行
-
–auth:启用认证模式
3. 配置文件方式启动 创建 /mongodb/conf/mongo.conf:
yaml
systemLog:
destination: file
path: /mongodb/log/mongod.log
logAppend: true
storage:
dbPath: /mongodb/data
engine: wiredTiger
journal:
enabled: true
net:
bindIp: 0.0.0.0
port: 27017
processManagement:
fork: true
启动命令:mongod -f /mongodb/conf/mongo.conf
2.2 MongoDB Shell (mongosh) 使用
注意:MongoDB 6.0移除了旧的mongo客户端,改用全新的mongosh。
安装与连接:
bash
# CentOS安装mongosh
wget https://downloads.mongodb.com/compass/mongodb-mongosh-1.8.0.x86_64.rpm
yum install -y mongodb-mongosh-1.8.0.x86_64.rpm
# 三种连接方式
mongosh –host=192.168.65.206 –port=27017
mongosh 192.168.65.206:27017
mongosh mongodb://192.168.65.206:27017/test
常用命令速查:
javascript
// 数据库操作
show dbs // 显示所有数据库
use test // 切换/创建数据库
db.dropDatabase() // 删除当前数据库
// 集合操作
show collections // 显示所有集合
db.createCollection("emp") // 创建集合
db.emp.drop() // 删除集合
db.emp.stats() // 查看集合详情
// 其他实用命令
show users // 显示用户列表
show roles // 显示角色列表
load("xxx.js") // 执行JavaScript脚本
db.help() // 数据库帮助
db.emp.help() // 集合帮助
2.3 安全认证配置
1. 创建管理员用户
javascript
// 切换到admin数据库
use admin
// 创建root权限用户
db.createUser({
user: "fox",
pwd: "fox",
roles: ["root"]
})
2. 常用权限角色
-
read/readWrite:指定数据库读/写权限
-
dbAdmin:数据库管理函数(索引创建删除等)
-
dbOwner:数据库任意操作权限
-
userAdmin:用户管理权限
-
clusterAdmin:集群管理权限(仅admin库)
-
readWriteAnyDatabase:所有数据库读写权限(仅admin库)
-
root:超级权限(仅admin库)
3. 启用鉴权启动
bash
mongod -f /mongodb/conf/mongo.conf –auth
4. 认证连接
bash
mongosh 192.168.65.206:27017 -u fox -p fox –authenticationDatabase=admin
2.4 Docker快速部署
bash
# 拉取MongoDB 6.0.5镜像
docker pull mongo:6.0.5
# 运行容器(启用认证)
docker run –name mongo-server -p 29017:27017 \\
-e MONGO_INITDB_ROOT_USERNAME=fox \\
-e MONGO_INITDB_ROOT_PASSWORD=fox \\
-d mongo:6.0.5 –wiredTigerCacheSizeGB 1
# 连接测试
mongosh ip:29017 -u fox -p fox
2.5 图形化管理工具推荐
MongoDB Compass:官方GUI,免费功能强大
Robo 3T:免费开源,轻量级
Studio 3T:功能全面,提供30天试用
数据库工具包:包含mongostat(性能监控)、mongotop(热点监控)等命令行工具
三、文档操作:CRUD全解析
3.1 插入文档
单条插入:
javascript
db.books.insertOne(
{name: "fox", age: 35},
{
writeConcern: {
w: "majority", // 写入大多数节点
j: true, // 持久化到磁盘
wtimeout: 5000 // 5秒超时
}
}
)
批量插入:
javascript
// 生成50条测试数据
var tags = ["nosql", "mongodb", "document", "developer", "popular"];
var types = ["technology", "sociality", "travel", "novel", "literature"];
var books = [];
for(var i = 0; i < 50; i++) {
var typeIdx = Math.floor(Math.random() * types.length);
var tagIdx = Math.floor(Math.random() * tags.length);
var favCount = Math.floor(Math.random() * 100);
books.push({
title: "book-" + i,
type: types[typeIdx],
tag: tags[tagIdx],
favCount: favCount,
author: "xxx" + i
});
}
// 执行批量插入
db.books.insertMany(books, {ordered: true}); // ordered: 是否按顺序插入
// 或者从文件加载
load("books.js")
3.2 查询文档
基础查询:
javascript
// 查询所有文档
db.books.find()
// 格式化显示
db.books.find().pretty()
// 查询单个文档
db.books.findOne({tag: "nosql"})
条件查询对照表:
| a = 1 | {a: 1} |
| a ≠ 1 | {a: {$ne: 1}} |
| a > 1 | {a: {$gt: 1}} |
| a ≥ 1 | {a: {$gte: 1}} |
| a < 1 | {a: {$lt: 1}} |
| a ≤ 1 | {a: {$lte: 1}} |
| a = 1 AND b = 1 | {a: 1, b: 1} 或 {$and: [{a: 1}, {b: 1}]} |
| a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |
| a IS NULL | {a: {$exists: false}} |
| a IN (1,2,3) | {a: {$in: [1, 2, 3]}} |
正则表达式查询:
javascript
// 查询type包含"so"的文档
db.books.find({type: {$regex: "so"}})
// 简写方式
db.books.find({type: /so/})
排序与分页:
javascript
// 按收藏数降序排序
db.books.find({type: "travel"}).sort({favCount: -1}) // -1:降序,1:升序
// 分页:每页8条,查询第3页
db.books.find().skip(16).limit(8) // skip(16)跳过前16条(前两页)
分页优化技巧:
javascript
// 传统分页问题:skip性能差,count数据量大时慢
// 优化方案:基于_id的连续查询
var pageSize = 10;
// 第一页
var page1 = db.books.find().sort({_id: 1}).limit(pageSize);
var lastId = page1[page1.length – 1]._id;
// 第二页(使用上一页最后一条记录的_id)
var page2 = db.books.find({_id: {$gt: lastId}})
.sort({_id: 1})
.limit(pageSize);
3.3 更新文档
更新操作符:
| $set | {$set: {field: value}} | 设置字段值(不存在则创建) |
| $unset | {$unset: {field: 1}} | 删除字段 |
| $inc | {$inc: {field: value}} | 数值字段增减 |
| $rename | {$rename: {old: new}} | 重命名字段 |
| $push | {$push: {field: value}} | 向数组追加元素 |
| $pull | {$pull: {field: value}} | 从数组删除元素 |
| $addToSet | {$addToSet: {field: value}} | 向数组添加元素(排重) |
更新示例:
javascript
// 更新单个文档(收藏数+1)
db.books.updateOne(
{_id: ObjectId("642e62ec933c0dca8f8e9f60")},
{$inc: {favCount: 1}}
)
// upsert:不存在则插入
db.books.updateOne(
{title: "my book"},
{$set: {tags: ["nosql", "mongodb"], type: "none", author: "fox"}},
{upsert: true} // 关键参数
)
// 更新多个文档
db.books.updateMany(
{type: "novel"},
{$set: {publishedDate: new Date()}}
)
// findAndModify:查询并修改,返回修改前/后的文档
db.books.findAndModify({
query: {_id: ObjectId("642ec31813bdda928a1ea2a8")},
update: {$inc: {favCount: 1}},
new: true // true:返回修改后,false:返回修改前(默认)
})
3.4 删除文档
javascript
// 删除单个文档
db.books.deleteOne({type: "novel"})
// 删除多个文档
db.books.deleteMany({type: "novel"})
// 删除集合所有文档
db.books.deleteMany({})
// 高效删除整个集合(比分批删除快)
db.books.drop()
// 返回被删除的文档
db.books.findOneAndDelete(
{type: "novel"},
{sort: {favCount: 1}} // 按收藏数升序删除第一个
)
3.5 批量操作
javascript
// bulkWrite实现批量操作
db.pizzas.bulkWrite([
{
insertOne: {
document: {_id: 3, type: "beef", size: "medium", price: 6}
}
},
{
updateOne: {
filter: {type: "cheese"},
update: {$set: {price: 8}}
}
},
{
deleteOne: {filter: {type: "pepperoni"}}
},
{
replaceOne: {
filter: {type: "vegan"},
replacement: {type: "tofu", size: "small", price: 4}
}
}
])
四、数据类型详解:超越JSON的BSON
4.1 BSON:MongoDB的二进制JSON
为什么选择BSON而不是JSON?
更高效的遍历:编码时记录元素长度,支持seek操作直接读取
更丰富的数据类型:扩展了日期、二进制、ObjectId等类型
轻量级语义:保持JSON的简洁性,支持嵌套和数组结构
BSON限制:
-
单个文档最大16MB
-
嵌套级别不超过100层
4.2 日期类型
javascript
// 创建日期字段(存储为UTC时间)
db.events.insert({
name: "发布会",
date: new Date(), // 方式1:JavaScript Date对象
created: ISODate() // 方式2:MongoDB ISODate
})
// 查询:默认返回的是UTC时间
db.events.find()
4.3 ObjectId:分布式唯一标识
ObjectId结构(12字节=24位十六进制):
-
4字节:Unix时间戳(秒)
-
5字节:随机数(机器号+进程号)
-
3字节:计数器(初始化随机)
javascript
// 生成和操作ObjectId
var id = ObjectId() // 生成新ObjectId
id.getTimestamp() // 获取创建时间
id.toString() // 转换为字符串
id.str // 十六进制字符串表示
// 客户端通常自动生成_id
db.collection.insert({name: "test"}) // 自动补充_id字段
4.4 内嵌文档与数组
内嵌文档示例:
javascript
// 插入包含作者信息的书籍
db.books.insert({
title: "撒哈拉的故事",
author: {
name: "三毛",
gender: "女",
hometown: "重庆"
}
})
// 查询内嵌字段
db.books.find({"author.name": "三毛"})
// 更新内嵌字段
db.books.updateOne(
{"author.name": "三毛"},
{$set: {"author.hometown": "重庆/台湾"}}
)
数组操作示例:
javascript
// 添加标签数组
db.books.updateOne(
{"author.name": "三毛"},
{$set: {tags: ["旅行", "随笔", "散文", "爱情", "文学"]}}
)
// 数组末尾追加元素
db.books.updateOne(
{"author.name": "三毛"},
{$push: {tags: "猎奇"}}
)
// 批量追加元素
db.books.updateOne(
{"author.name": "三毛"},
{$push: {tags: {$each: ["伤感", "想象力"]}}}
)
// 追加后只保留最后3个元素
db.books.updateOne(
{"author.name": "三毛"},
{$push: {tags: {
$each: ["伤感", "想象力"],
$slice: -3 // 只保留最后3个
}}}
)
// 数组查询
db.books.find({tags: "伤感"}) // 包含"伤感"
db.books.find({tags: {$all: ["伤感", "想象力"]}}) // 同时包含
复杂数组结构(商品属性示例):
javascript
// 商品多属性表示
db.goods.insertMany([
{
name: "羽绒服",
tags: [
{tagKey: "size", tagValue: ["M", "L", "XL", "XXL", "XXXL"]},
{tagKey: "color", tagValue: ["黑色", "宝蓝"]},
{tagKey: "style", tagValue: "韩风"}
]
},
{
name: "羊毛衫",
tags: [
{tagKey: "size", tagValue: ["L", "XL", "XXL"]},
{tagKey: "color", tagValue: ["蓝色", "杏色"]},
{tagKey: "style", tagValue: "韩风"}
]
}
])
// 数组元素匹配查询
db.goods.find({
tags: {
$elemMatch: {tagKey: "color", tagValue: "黑色"}
}
})
// 组合条件查询
db.goods.find({
tags: {
$all: [
{$elemMatch: {tagKey: "color", tagValue: "黑色"}},
{$elemMatch: {tagKey: "size", tagValue: "XL"}}
]
}
})
4.5 固定集合:FIFO数据队列
创建固定集合:
javascript
// 创建最多10条文档或4KB大小的固定集合
db.createCollection("logs", {
capped: true, // 固定集合
size: 4096, // 最大4KB(必选)
max: 10 // 最多10条文档(可选)
})
特性与应用:
FIFO原则:新文档插入末尾,空间不足时覆盖最旧文档
高性能:写入顺序固定,无需索引维护开销
自动淘汰:无需手动清理旧数据
应用场景:
-
系统日志存储
-
实时数据流(如股票价格)
-
最新N条记录缓存
股票行情系统示例:
javascript
// 1. 创建消息队列集合
db.createCollection("stock_queue", {
capped: true,
size: 10485760 // 10MB容量
})
// 2. 创建时间索引
db.stock_queue.createIndex({timestamp: 1})
// 3. 生产者模拟(每秒发布股价)
function pushEvent() {
while(true) {
db.stock_queue.insert({
timestamp: new Date(),
stock: "MongoDB Inc",
price: 100 * Math.random(1000)
});
print("publish stock changed");
sleep(1000);
}
}
// 4. 消费者监听(类似tail -f)
function listen() {
var cursor = db.stock_queue
.find({timestamp: {$gte: new Date()}})
.tailable(); // 创建可追加游标
while(true) {
if(cursor.hasNext()) {
print(JSON.stringify(cursor.next(), null, 2));
}
sleep(1000);
}
}
五、Spring Boot整合:Java开发者实践
5.1 环境配置
1. Maven依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2. 配置文件(application.yml)
yaml
spring:
data:
mongodb:
# URI方式(推荐)
uri: mongodb://fox:fox@192.168.65.174:27017/test?authSource=admin
# 拆分方式(等价于上面URI)
# database: test
# host: 192.168.65.174
# port: 27017
# username: fox
# password: fox
# authentication-database: admin
3. 实体类映射
java
@Document("emp") // 对应emp集合
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
@Id // 映射为文档_id
private Integer id;
@Field("username") // 指定字段名
private String name;
@Field // 默认使用字段名
private int age;
@Field
private Double salary;
@Field
private Date entryDay;
@Transient // 不持久化到数据库
private String tempField;
}
5.2 核心操作示例
1. 集合操作
java
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void testCollection() {
// 检查集合是否存在
boolean exists = mongoTemplate.collectionExists("emp");
if (exists) {
// 删除集合
mongoTemplate.dropCollection("emp");
}
// 创建集合
mongoTemplate.createCollection("emp");
}
2. 文档CRUD
java
// 插入文档
@Test
public void testInsert() {
// 插入单条(_id存在时抛异常)
Employee emp1 = new Employee(1, "小明", 30, 10000.00, new Date());
mongoTemplate.insert(emp1);
// 插入多条
List<Employee> list = Arrays.asList(
new Employee(2, "张三", 21, 5000.00, new Date()),
new Employee(3, "李四", 26, 8000.00, new Date()),
new Employee(4, "王五", 22, 8000.00, new Date())
);
mongoTemplate.insert(list, Employee.class);
// save方法:_id存在时更新
// mongoTemplate.save(employee);
}
// 查询文档
@Test
public void testFind() {
// 查询所有
List<Employee> all = mongoTemplate.findAll(Employee.class);
// 根据_id查询
Employee emp = mongoTemplate.findById(1, Employee.class);
// 条件查询
// 薪资>=8000
Query query1 = new Query(Criteria.where("salary").gte(8000));
// 薪资>4000且<10000
Query query2 = new Query(Criteria.where("salary").gt(4000).lt(10000));
// 模糊查询
Query query3 = new Query(Criteria.where("name").regex("张"));
// AND查询:年龄>25且薪资>8000
Criteria criteria1 = new Criteria();
criteria1.andOperator(
Criteria.where("age").gt(25),
Criteria.where("salary").gt(8000)
);
// OR查询:姓名为张三或薪资>5000
Criteria criteria2 = new Criteria();
criteria2.orOperator(
Criteria.where("name").is("张三"),
Criteria.where("salary").gt(5000)
);
// 排序和分页
Query query = new Query(criteria2)
.with(Sort.by(Sort.Order.desc("salary"))) // 按薪资降序
.skip(0) // 跳过0条
.limit(4); // 每页4条
List<Employee> result = mongoTemplate.find(query, Employee.class);
// JSON方式查询
String json = "{$or: [{age: {$gt: 25}}, {salary: {$gte: 8000}}]}";
Query jsonQuery = new BasicQuery(json);
List<Employee> jsonResult = mongoTemplate.find(jsonQuery, Employee.class);
}
// 更新文档
@Test
public void testUpdate() {
// 查询条件:薪资>=15000
Query query = new Query(Criteria.where("salary").gte(15000));
// 更新操作:薪资设置为13000
Update update = new Update();
update.set("salary", 13000);
// 更新第一条匹配记录
// UpdateResult result = mongoTemplate.updateFirst(query, update, Employee.class);
// 更新所有匹配记录
// UpdateResult result = mongoTemplate.updateMulti(query, update, Employee.class);
// upsert:不存在则插入
update.setOnInsert("id", 11); // 指定插入时的_id
UpdateResult result = mongoTemplate.upsert(query, update, Employee.class);
System.out.println("修改记录数:" + result.getModifiedCount());
}
// 删除文档
@Test
public void testDelete() {
// 删除所有文档
// mongoTemplate.remove(new Query(), Employee.class);
// 条件删除:薪资<10000
Query query = new Query(Criteria.where("salary").lt(10000));
mongoTemplate.remove(query, Employee.class);
}
5.3 高级配置:去除_class属性
Spring Data MongoDB默认会在文档中添加_class字段存储Java类全限定名,可通过以下配置去除:
java
@Configuration
public class MongoConfig {
@Bean
public MappingMongoConverter mappingMongoConverter(
MongoDatabaseFactory mongoDatabaseFactory,
MongoMappingContext context,
MongoCustomConversions conversions) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, context);
converter.setCustomConversions(conversions);
// 关键配置:设置TypeMapper,去除_class字段
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
}
六、MongoDB 6.0 新特性概览
时序集合增强:更好的时间序列数据处理能力
Change Stream增强:更强大的实时数据变更监听
可查询加密:客户端字段级加密,提升数据安全
聚合&查询能力增强:更丰富的聚合管道操作符
集群同步优化:改进的集群数据同步机制
七、最佳实践总结
7.1 设计原则
适度反规范化:合理使用内嵌文档减少查询次数
预聚合常用数据:避免实时计算开销
索引策略:为查询字段创建索引,但避免过度索引
写关注配置:根据业务需求调整writeConcern级别
7.2 性能优化
分页优化:避免使用skip进行深度分页
投影优化:只查询需要的字段
批量操作:使用bulkWrite减少网络往返
连接池管理:合理配置连接池参数
7.3 监控与维护
定期检查慢查询:使用数据库Profiler
监控硬件资源:CPU、内存、磁盘IO
备份策略:定期备份,测试恢复流程
版本升级:测试环境先行,生产环境分批升级




