MongoDB
MongoDB 是面向文档的 NoSQL 数据库,使用 BSON(二进制 JSON)格式存储数据。与关系型数据库不同,MongoDB 不要求预定义表结构(schema-less),同一个集合中的文档可以有不同的字段。
适用场景:内容管理系统(CMS)、日志存储与分析、用户画像与行为数据、物联网数据采集、需要快速迭代的产品原型。当数据结构频繁变化或需要存储嵌套文档时,MongoDB 比关系型数据库更灵活。
核心要点
面向文档的NoSQL数据库,使用BSON格式存储数据,支持灵活的数据模型
核心概念
| 概念 | 说明 |
|---|---|
| Document | 文档,MongoDB 的最小数据单元,类似于 JSON 对象 |
| Collection | 集合,多个文档组成,类似于关系型数据库的表 |
| Database | 数据库,集合的容器 |
与关系型数据库的对应关系
| MongoDB | 关系型数据库 |
|---|---|
| Database(数据库) | Schema(库) |
| Collection(集合) | Table(表) |
| Document(文档) | Row(行) |
| Field(字段) | Column(列) |
| 无固定 schema | 建表时定 schema |
BSON 与 JSON 的区别
BSON(Binary JSON)是 JSON 的二进制序列化形式,解决了 JSON 的两个问题:存储空间——二进制编码比文本 JSON 更紧凑;类型支持——原生支持 Date、ObjectId、Decimal128 等 JSON 没有的类型,并支持快速字段定位(不用逐字符解析文本)。因此 MongoDB 存取速度比纯文本 JSON 快得多。
数据类型
| 类型 | 说明 |
|---|---|
| String | 字符串 |
| Number | 数值(整数/浮点) |
| Boolean | 布尔值 |
| Array | 数组 |
| Object | 嵌套文档 |
| Null | 空值 |
| Date | 日期 |
| ObjectId | 对象 ID |
快速开始
Docker 启动
docker run -d --name mongodb \
-p 27017:27017 \
-v ~/mongo/data:/data/db \
mongo:latest基本操作
show dbs // 显示所有数据库
use mydb // 切换/创建数据库
show collections // 显示当前数据库的集合
db.createCollection("users") // 创建集合
db.users.drop() // 删除集合CRUD 操作
Create(插入)
// 插入单条
db.users.insertOne({
name: "张三",
age: 25,
email: "zhangsan@example.com",
skills: ["Java", "Python"],
createdAt: new Date()
})
// 插入多条
db.users.insertMany([
{ name: "李四", age: 30 },
{ name: "王五", age: 28 }
])Read(查询)
db.users.find() // 查询所有
db.users.find({ age: { $gte: 18 } }) // 条件查询
db.users.findOne({ name: "张三" }) // 查询单个
db.users.find({}, { name: 1, email: 1 }) // 投影
db.users.find().skip(10).limit(10) // 分页
db.users.find().sort({ age: -1 }) // 排序(-1降序,1升序)
db.users.countDocuments({ age: { $gte: 18 } }) // 统计
db.users.distinct("name") // 去重常用查询运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
| $eq | 等于 | { age: { $eq: 20 } } |
| $ne | 不等于 | { age: { $ne: 20 } } |
| $gt | 大于 | { age: { $gt: 20 } } |
| $gte | 大于等于 | { age: { $gte: 20 } } |
| $lt | 小于 | { age: { $lt: 20 } } |
| $lte | 小于等于 | { age: { $lte: 20 } } |
| $in | 在数组中 | { age: { $in: [18, 20, 25] } } |
| $nin | 不在数组中 | { age: { $nin: [18, 20] } } |
| $and | 逻辑与 | { $and: [{ age: { $gt: 18 } }, { name: "张三" }] } |
| $or | 逻辑或 | { $or: [{ age: { $lt: 20 } }, { name: "张三" }] } |
| $exists | 字段存在 | { email: { $exists: true } } |
| $regex | 正则匹配 | { name: { $regex: "^张" } } |
Update(更新)
db.users.updateOne(
{ name: "张三" },
{ $set: { age: 26 } }
)
db.users.updateMany(
{ age: { $lt: 18 } },
{ $set: { status: "未成年" } }
)常用更新操作符:$set 更新字段、$inc 递增/递减、$push 添加到数组、$pull 从数组移除、$addToSet 添加到数组(不重复)。
Delete(删除)
db.users.deleteOne({ name: "张三" })
db.users.deleteMany({ age: { $lt: 18 } })
db.users.drop() // 删除集合
db.dropDatabase() // 删除数据库聚合管道
聚合管道用于复杂的数据处理:
db.orders.aggregate([
{ $match: { status: "completed" } }, // 筛选
{ $unwind: "$items" }, // 展开数组
{ $group: { // 分组聚合
_id: "$userId",
totalAmount: { $sum: "$items.price" },
orderCount: { $sum: 1 }
}},
{ $sort: { totalAmount: -1 } }, // 排序
{ $limit: 10 } // 限制数量
])常用聚合阶段:$match 筛选、$group 分组聚合、$sort 排序、$limit 限制数量、$skip 跳过、$unwind 展开数组、$project 投影、$lookup 左连接、$count 计数、$addFields 添加字段。
索引
创建索引
db.users.createIndex({ email: 1 }, { unique: true }) // 单字段索引
db.users.createIndex({ age: 1, name: 1 }) // 复合索引
db.users.createIndex({ skills: 1 }) // 多键索引(数组字段)
db.users.createIndex({ bio: "text" }) // 文本索引
db.users.getIndexes() // 查看索引
db.users.dropIndex("索引名") // 删除索引索引类型
| 类型 | 说明 |
|---|---|
| 单字段索引 | 在单个字段上创建 |
| 复合索引 | 在多个字段上创建 |
| 多键索引 | 在数组字段上创建 |
| 文本索引 | 全文搜索 |
| 2dsphere 索引 | 地理空间查询 |
| TTL 索引 | 到期自动删除文档,适合会话、验证码、日志等有时效的数据 |
TTL 索引示例:给 expireAt 字段建 TTL 索引,后台线程每秒扫描并删除过期文档:
db.sessions.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 })
// 插入时 expireAt 设为过期时间点,到点文档自动被清理高可用与扩展
复制集(Replica Set)
复制集是 MongoDB 的高可用方案:一个 Primary 节点(读写)+ 多个 Secondary 节点(只读副本),Primary 宕机后剩余节点投票(多数派)自动选出新 Primary,无需人工干预。客户端连接的是复制集名称而非单个节点,驱动会自动感知主从切换。
// 启动复制集节点(配置文件中指定 replSet 名称)
// rs.initiate() 初始化,rs.add("host:port") 添加成员
rs.status() // 查看复制集状态分片集群(Sharding)
分片集群是 MongoDB 的水平扩展方案:按分片键(shard key)把数据切分到多个分片上,路由服务 mongos 负责把请求转发到对应分片,配置服务器记录元数据。单机数据量过大或写入压力过高时引入,分片键的选择直接决定数据分布是否均匀——需选基数高、访问均匀的字段。
应用 → mongos(路由)→ 分片1 / 分片2 / 分片3
↑ 每个分片通常是独立复制集事务支持
MongoDB 4.0+ 支持多文档事务:在复制集内对多个文档的读写要么全部成功、要么全部回滚(4.2+ 支持分片集群)。事务内操作要求集合在复制集环境且引擎为 WiredTiger。虽然有了事务,但 MongoDB 的定位仍是文档数据库——频繁强事务的业务仍建议选关系型数据库。
Spring Boot 集成
Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>配置
spring:
data:
mongodb:
uri: mongodb://localhost:27017/mydb实体类
@Document(collection = "users")
public class User {
@Id
private String id;
@Field("name")
private String name;
private Integer age;
private List<String> skills;
private Address address; // 嵌套文档
}
@Document
class Address {
private String city;
private String street;
}Repository
public interface UserRepository extends MongoRepository<User, String> {
User findByName(String name);
List<User> findByAgeBetween(Integer min, Integer max);
List<User> findByNameContaining(String keyword);
@Query("{ 'age': { $gte: ?0 } }")
List<User> findByAgeGreaterThan(Integer age);
}MongoTemplate
@Autowired
private MongoTemplate mongoTemplate;
// 插入
User user = new User("张三", 25);
mongoTemplate.insert(user);
// 查询
Query query = new Query(Criteria.where("age").gte(18));
List<User> users = mongoTemplate.find(query, User.class);
// 更新
Query query = new Query(Criteria.where("name").is("张三"));
Update update = new Update().set("age", 30);
mongoTemplate.updateFirst(query, update, User.class);常见问题
ObjectId 与自增主键
MongoDB 默认使用 ObjectId(12 字节,包含时间戳、机器 ID、进程 ID、计数器)作为 _id。ObjectId 是分布式的,无需协调即可全局唯一,但不如自增 ID 有序。高写入场景下,ObjectId 的单调递增特性有利于 B-Tree 索引性能。
内存不足
MongoDB 用 WiredTiger 存储引擎,工作集(频繁访问的数据)需要常驻缓存,缓存不够时大量读请求直接落盘,性能骤降。默认缓存大小为 (RAM - 1GB) / 2,与机器内存强相关;用 db.serverStatus().wiredTiger.cache 查看命中率,长期偏低说明工作集超出缓存。必要时调大 wiredTigerCacheSizeGB,但要为操作系统页缓存(也参与磁盘缓存)留足内存。
慢查询排查
慢查询最常见的原因是未命中索引——MongoDB 对无索引字段的查询做全集合扫描(COLLSCAN),数据量大时必然慢。用 db.setProfilingLevel(1, { slowms: 100 }) 开启慢查询日志,db.system.profile.find().sort({ts:-1}).limit(10) 查看最近的慢查询,从日志里确认是否出现 COLLSCAN;为常用查询字段建立索引后,用 explain() 验证是否走 IXSCAN。