MongoDB 基础概念

MongoDB · 基础概念

1.1 是什么

MongoDB 是面向文档(Document-Oriented)的 NoSQL (Not Only SQL,非关系型数据库统称)数据库,用类 JSON 的 BSON 格式存储数据。

  • 数据层级:DatabaseCollectionDocumentField
    (对比 MySQL:DatabaseTableRowColumn
  • 存储格式:BSON(Binary JSON),二进制、带类型和长度前缀
  • 不要求固定表结构,同一集合内的文档字段可以不同

BSON vs JSON

维度 JSON BSON
格式 文本 二进制
类型 基础类型 额外支持 Date / ObjectId(12字节,MongoDB默认主键) / Decimal128(高精度十进制) / Binary / Timestamp / 正则
解析 需全量解析 二进制格式下,每个文档和值都有长度前缀,比如字符串前面有 4 字节表示长度。不需要像 JSON 那样逐字符扫描,可以直接读到长度,然后跳过整个值。如果只需要查找某个字段,可以快速跳过不相关的字段,因此遍历和解析效率更高
体积 较小 通常略大(额外存了类型和长度)

1.2 特点

特点 说明
文档模型 一条记录就是一个完整对象,天然贴合 Java 对象,减少 JOIN
弱 Schema 字段可动态增减,适合快速迭代;代价是需要自己约束数据质量
高读写吞吐 内存映射 + 文档级并发控制 + 顺序写 journal
水平扩展 原生分片,加机器自动均衡,不依赖中间件
高可用 副本集自动故障转移,无需额外组件
丰富查询 支持二级索引、范围查询、聚合管道、地理空间、全文检索
多文档事务 4.0 起支持副本集事务,4.2 起支持分片集群事务

1.3 应用场景及优势

适合

  • 字段不固定、结构频繁变化的业务(商品详情、配置、表单)
  • 日志 / 埋点 / 监控数据(写入量大、按时间查询、可 TTL 清理)
  • IM 消息、评论、Feed 流(天然文档结构 + 游标分页)
  • 物联网 / 设备上报(高并发写入 + 地理空间查询)
  • 内容管理、爬虫结果存储(嵌套结构多)

不适合

  • 强事务、强一致的核心账务(金额、订单流水)
  • 大量多表关联的复杂报表分析
  • 需要复杂 JOIN + 聚合的 OLAP 场景

优势:以「单文档」为单位读写,用内嵌冗余换掉 JOIN,用原生分片换掉分库分表中间件。


1.4 与 MySQL 的对比记忆

维度 MySQL MongoDB
数据模型 表 / 行 / 列,强 Schema 集合 / 文档,弱 Schema
索引结构 B+ 树 B 树
锁粒度 行锁 + MVCC 文档级并发(乐观并发控制)
事务 默认支持,成熟稳定 4.0+ 才支持,有性能损耗
关联 JOIN $lookup,性能差,靠内嵌冗余替代
扩展方式 垂直为主,分库分表靠中间件 原生分片,自动均衡
一致性 默认强一致 默认 CP,可调最终一致
单条记录上限 行约 65535 字节(不含 BLOB/TEXT) 文档 16MB
典型选型 强事务 / 复杂报表 / 核心账务 弱结构 / 高吞吐 / 日志 / 内容 / IM

选型口诀:要事务、要关联、要报表 → MySQL;要吞吐、要弹性、要结构自由 → MongoDB。


1.5 文档

MongoDB 中最基本的数据单元,相当于关系型数据库中的一行记录。

文档是一个有序的键值对集合,用 BSON 存储,支持嵌套和数组。

特点

  • 结构灵活:不同文档的字段可以完全不同,无需固定表结构。
  • 支持嵌套:字段的值可以是另一个文档、数组,甚至嵌套多层。
  • 主键 _id:每个文档必须有一个唯一的 _id 字段,默认自动生成 ObjectId(12 字节唯一 ID 类型)。

示例

json 复制代码
{
  "_id": ObjectId("5f8d8c8b8f8b8b8b8b8b8b8b"),
  "name": "Alice",
  "age": 30,
  "address": {
    "city": "Beijing",
    "province": "Beijing"
  },
  "hobbies": ["reading", "coding"]
}

一句话总结:文档是MongoDB 的基本数据单元,类似关系型数据库的一行,但字段可以动态变化。用 BSON 存储,支持嵌套和数组。


1.6 集合

文档的分组,相当于关系型数据库中的表。

一个集合中可以存放结构不同的文档。

特点

  • 无模式(Schema-less):不需要预先定义字段和类型,写入时动态决定。
  • 命名规则:不能以 system. 开头,不能包含 $ 和空字符,区分大小写。
  • 动态创建:向一个不存在的集合插入文档时,MongoDB 会自动创建它(若没有开启多文档事务会自动创建,开启多文档事务后最好手动创建)。

与 MySQL 表的对比

维度 MySQL 表 MongoDB 集合
结构 固定列、行 无固定结构,文档可不同
修改结构 ALTER TABLE 无需修改,直接写入新字段
数据单元 文档

一句话总结:文档的容器,类似表。但它没有固定结构,字段可以随文档变化。第一次插入数据时会自动创建集合


1.7 ObjectId

MongoDB 文档默认的 _id 类型,是一个 12 字节的 BSON 对象,用于在分布式环境中生成全局唯一 ID。

12 字节结构

字节 含义
0-3 Unix 时间戳(秒级)
4-6 机器标识(主机名哈希)
7-8 进程 ID
9-11 随机增量计数器

特点

  • 趋势递增:前 4 字节是时间戳,因此 ObjectId 大致按创建时间递增,对索引友好。
  • 分布式唯一:不同机器、不同进程生成的 ObjectId 不会冲突。
  • 无需中央协调:不需要像 MySQL 自增主键那样依赖数据库单点。
  • 客户端可生成:驱动程序可以在客户端生成,减少数据库压力。

与 MySQL 自增主键对比

维度 MySQL 自增主键 MongoDB ObjectId
生成方式 数据库集中生成 客户端/服务端独立生成
分布式扩展 需要分片或步长调整 天然分布式唯一
有序性 严格递增 趋势递增
类型 整数 12 字节二进制
空间占用 4/8 字节 12 字节

一句话总结:MongoDB 默认的主键类型,12 字节,包含时间戳、机器标识、进程 ID 和随机计数器。它保证分布式唯一且趋势递增,适合分片和水平扩展。相比 MySQL 自增主键,它不依赖中心节点,但占用空间更大。

1.8 增删改查常见写法

mongosh

javascript 复制代码
// ---------- 增 ----------
db.user.insertOne({ name: "张三", age: 25, tags: ["vip", "new"] })
db.user.insertMany([{ name: "李四", age: 30 }, { name: "王五", age: 17 }])

// ---------- 查 ----------
db.user.find({ age: { $gte: 18 } }).sort({ age: -1 }).limit(10)
db.user.findOne({ _id: ObjectId("650000000000000000000001") })

db.user.find({ tags: "vip" })                          // 数组包含某元素
db.user.find({ tags: { $all: ["vip", "new"] } })       // 数组同时包含多个
db.user.find({ tags: { $size: 2 } })                   // 数组长度
db.user.find({ tags: { $elemMatch: { $eq: "vip" } } }) // 数组元素多条件
db.user.find({ "addr.city": "杭州" })                   // 嵌套字段(点号)

db.user.find({ $or: [{ age: 18 }, { name: "张三" }] })
db.user.find({ age: { $in: [18, 19, 20] } })
db.user.find({ name: { $regex: "^张" } })              // 前缀匹配才走索引
db.user.find({}, { name: 1, age: 1, _id: 0 })          // 投影(1 包含 / 0 排除,不能混用)
db.user.countDocuments({ age: { $gt: 18 } })

// ---------- 改 ----------
db.user.updateOne({ name: "张三" }, { $set: { age: 26 } })
db.user.updateMany({ age: { $lt: 18 } }, { $set: { type: "minor" } })
db.user.updateOne({ name: "张三" }, { $inc: { score: 10 }, $push: { tags: "vvip" } })
db.user.updateOne({ name: "李四" }, { $set: { age: 30 } }, { upsert: true }) // 不存在则插入
db.user.replaceOne({ name: "张三" }, { name: "张三", age: 26 })              // 整体替换

// ---------- 删 ----------
db.user.deleteOne({ name: "张三" })
db.user.deleteMany({ age: { $lt: 18 } })
db.user.drop()                                         // 删整个集合

Java(Spring Data MongoDB)

java 复制代码
// ---------- 增 ----------
mongoTemplate.insert(user);
mongoTemplate.insert(userList, User.class);

// ---------- 查 ----------
Query q = Query.query(Criteria.where("age").gte(18))
               .with(Sort.by(Sort.Direction.DESC, "age"))
               .limit(10);
List<User> list = mongoTemplate.find(q, User.class);

// -----------根据查询条件查询指定字段---------------

Query query = new Query();
query.addCriteria(Criteria.where("status").is("ACTIVE")
                          .and("age").gte(18)); // 1. 构建查询条件

query.fields()
     .include("name")
     .include("age")
     .exclude("_id"); // 2. 指定返回字段(只返回 name 和 age,不返回 _id)

List<User> users = mongoTemplate.find(query, User.class, "user"); // 3. 执行查询,映射到指定实体或 Map
List<Map> maps = mongoTemplate.find(query, Map.class, "user"); // 或者返回 Map

// ---------- 改 ----------
Update u = new Update().set("age", 26).inc("score", 10).push("tags", "vvip");
mongoTemplate.updateFirst(Query.query(Criteria.where("name").is("张三")), u, User.class);
mongoTemplate.updateMulti(Query.query(Criteria.where("age").lt(18)), u, User.class);
mongoTemplate.upsert(Query.query(Criteria.where("name").is("李四")), u, User.class);

// ---------- 删 ----------
mongoTemplate.remove(Query.query(Criteria.where("name").is("张三")), User.class);
相关推荐
2601_962218611 小时前
万象生鲜系统多终端统一数据协议PC手机PDA数据实时同步
大数据·数据库·人工智能·python·算法
我滴老baby1 小时前
部署 Excalidraw:Docker 搭建手绘白板,再配置固定公网访问
数据库·人工智能·架构
泡海椒1 小时前
jquick-pdf 文本元素实战:段落、行内文本、制表符用法详解
java·开发语言·pdf
摇滚侠1 小时前
《Spring Boot 3:高级与架构设计》第 1 章 元编程与元信息 个人理解 1
java·spring boot·笔记·后端
吃饱了得干活1 小时前
Java 单点登录实战:一条主线看懂 Session 共享、CAS 与 OAuth2+JWT
java·后端
xiaoqiMikko1 小时前
Jetty 又出一条走私(CVE-2026-19203):官方叫升 9.4.64,而 9.4.64 在 Central 上是 404
java·安全
Web4Browser1 小时前
浏览器指纹逆向到底在分析什么:从 JavaScript 采集、Canvas 与 WebGL 到环境一致性校验
java·服务器·前端
AKA__Zas2 小时前
跨网段主机的文件传输过程
java·网络·网络协议·学习方法
Wang's Blog2 小时前
Java 接入Redis: Redis简介与NoSQL定位
java·服务器·redis