MongoDB 从零到一(01):初识 MongoDB,文档数据库与选型定位
2026-10-12 09:40:00

适合人群:每天在写 MySQL,听说过 NoSQL 但没系统用过 MongoDB 的 Java 开发者。
本文基于 MongoDB 7.x 讲解,概念对照统一以 MySQL 8 为参照。先记住最重要的一条边界:MongoDB 是通用文档数据库,它改变的是”数据怎么组织”,而不是”要不要数据库”。

一、从一个熟悉的需求说起

几乎每个后端都维护过一张会不断”长胖”的商品表。最初它可能只有名称、价格、库存三个字段:

1
2
3
4
5
6
CREATE TABLE product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0
);

很快需求来了:手机要展示颜色和内存;图书要有作者、ISBN 和出版时间;服装要区分尺码。这些属性彼此不同、未来还会继续增加,通用的做法通常有两条路:

  1. 垂直拆表:product_base + 一张扩展属性表(attr_key / attr_value),查询时自己拼装;
  2. 加列:每次新需求就 ALTER TABLE 加一个 color、isbn、publish_time…… 没有该属性的行整列存 NULL。

两条路都能走通,也都难受:前者把简单查询变成多次关联和程序拼装;后者让表越来越宽、稀疏列越来越多,历史遗留字段最后没人敢删。数据的形状明明是”每个商品一份完整描述”,却被关系范式切成了”一行 + 若干键值对 + 若干可空列”。

MongoDB 对这类问题的回答很直接:既然一条业务记录天然是一个整体,那就按文档存储它。上面三类商品可以共存于同一个集合(collection)中:

1
2
3
4
5
6
7
// 数码类商品:嵌套规格对象
{ "name": "手机 X", "price": NumberDecimal("4999.00"), "stock": 120,
"spec": { "color": "黑", "memoryGB": 12 } }

// 图书类商品:完全不同的字段组合
{ "name": "深入理解 Java 虚拟机", "price": NumberDecimal("129.00"), "stock": 30,
"author": "周志明", "isbn": "978-7-111-64127-2" }

不需要预先声明共有列,也不需要为”暂时没有的属性”安排 NULL 占位——每份文档只描述自己的结构。这就是文档数据库最朴素的动机。

图1:数据库技术版图中 MongoDB 的定位

1.1 NoSQL 版图上的四大家族

NoSQL 不是一种技术,而是一组”放弃部分关系模型能力、换取特定场景优势”的方案。常见的四个分支可以按”数据长什么样”来记:

分支 代表产品 数据形态 典型用途
键值型(KV) Redis key → 简单值 缓存、计数器、分布式锁
文档型(Document) MongoDB 类 JSON 文档,支持嵌套与数组 业务主存储、内容管理、用户画像
宽列型(Wide-column) Cassandra、HBase 行键 + 大量动态列 日志流、海量写入的时序数据
图型(Graph) Neo4j 节点 + 边 社交关系、风控关联网络

MongoDB 属于文档型。它的特别之处在于通用性:Redis 通常做缓存放在链路旁边,Cassandra 和 Neo4j 服务特定形态的数据,而 MongoDB 有完整的索引、聚合、事务和多模扩展能力,足以承担”业务主存储”的角色——这是它与大多数 NoSQL 的本质区别。

1.2 别把它理解成”拒绝 SQL”

需要先澄清两个常见误读。第一,引入 MongoDB 不等于抛弃 MySQL:强事务场景(账户余额、订单状态机)依然适合放在关系库中。如果你还在纠结几种关系型数据库之间的差异,可以先读 MySQL、PostgreSQL(PG)与 Oracle:区别、SQL 对照与选型指南,那篇解决的是”选哪种 SQL”;本系列要补的是”什么时候连 SQL 都不必是默认答案”。

第二,MongoDB 与 Elasticsearch(ES)也不是竞争关系。两者的数据长得都像 JSON,但分工完全不同,我在 Elasticsearch 从零到一(01):全文检索与核心概念 里详细对比过:ES 是面向检索与分析构建的读模型,不承载事务事实;MongoDB 则是可以放心写入业务主数据的存储引擎。

1
2
3
下单/扣库存 → MySQL(强事务事实来源)
商品详情/用户画像 → MongoDB(灵活业务主存储)
搜索框输入关键词 → Elasticsearch(倒排索引读模型)

一句话:MySQL 管”对不对”,MongoDB 管”数据怎么长”,ES 管”找得到吗”。

二、BSON:文档的真实模样

上一节的示例以 JSON 形式呈现,但 MongoDB 在磁盘和网络中的真实格式是 BSON(Binary JSON)。纯 JSON 只有 string、number、boolean、null、array、object 六种形态,对数据库来说太粗糙了:金额用什么精度?日期是字符串还是毫秒数?整数和浮点如何区分?

BSON 在保留 JSON 直观性的同时补充了类型系统,常用的类型与 Java 近似对应如下:

BSON 类型 示例写法 Java 侧近似类型 使用提醒
String "name" String UTF-8 编码
Int32 / Int64 120 / NumberLong(1) Integer / Long 驱动自动选择
Double 3.14 Double 精度不可控
Decimal128 NumberDecimal("129.00") BigDecimal 金额一律用它,避免浮点误差
Date ISODate("2026-08-27T02:00:00Z") java.util.Date / Instant 内部存 UTC 毫秒数
ObjectId ObjectId("64f0a...") org.bson.types.ObjectId 默认 _id 生成策略
Array [1, 2, 3] List<T> 数组是一等公民,可直接建索引
Document(嵌套) { "color": "黑" } Map<String, Object> / 自定义 POJO 支持多层嵌套

图2:一份商品文档的字段构成与 BSON 类型标注

这份对照里有两点值得展开。

2.1 _id 与 ObjectId

每个文档都必须有一个 _id 字段作为集合内的唯一标识,相当于主键;写入时不指定的话,驱动会自动生成一个 ObjectId。它的 12 个字节并非随机数:

  • 前 4 字节是秒级时间戳,因此 ObjectId 整体上随时间递增;
  • 中间 5 字节是进程启动时生成的随机值,同一进程内不变;
  • 最后 3 字节是自增计数器,保证同进程同秒内不重复。

趋势递增这个特性在后面讲索引时会再次出现:按 _id 建立的默认索引附近写入,局部性好;但它也意味着 _id 本身不适合直接当分片键(所有新写入都会打到最后一片),这是后话。

2.2 16 MB 上限:文档不是越大越好

单个文档最大 16 MB。听起来很大(足够放下一部中等小说),但设计上仍然要求避免无界的增长:比如把帖子的一百万条评论全塞进帖子文档的数组里,迟早会撞上限制并拖垮读写性能。”关联数据何时嵌入、何时引用”有一整套建模方法论,第 04 篇会专门讨论;现在只需记住结论——**文档描述的是”一条业务记录”,不是一个业务域的全部数据。**超大的二进制文件则有 GridFS 协议负责切块存储。

三、概念对照:先把词汇表对齐

从 MySQL 过来,最大的门槛不是语法而是词汇。下面这张映射表贯穿整个系列,建议保存备查:

关系型(MySQL) MongoDB 说明
Database Database 库的概念一致
Table Collection(集合) 集合无需预定义结构
Row Document(文档) 一行 = 一个 BSON 文档
Column Field(字段) 同一集合内字段可以不同
Primary Key _id 未指定时自动生成 ObjectId
JOIN 嵌入文档 / $lookup 首选嵌入,其次管道联查
GROUP BY Aggregation Pipeline 聚合管道,第 05、06 篇的主角
索引 Index Index 同样基于 B-Tree,语法不同

表格能帮助入门,但有三处不能画等号的地方,恰恰是初学者最容易踩坑的位置。

3.1 集合 ≠ 表:动态 Schema 的自由与纪律

关系表的每一行必须符合建表时声明的列结构;MongoDB 集合则不做强制——同一个集合里,昨天带 author 字段的书、今天新加 edition 字段的书可以并存。这带来一个巨大的工程红利:应用迭代新增字段不再依赖 DBA 执行 DDL,灰度发布期间新旧两种结构的文档也能和平共处。

自由不等于放任。实践中,生产环境几乎都会用 Schema 校验(JSON Schema Validator)为集合声明字段的类型约束,让”灵活”发生在规划内,而不是退化成无法维护的垃圾场。这与前端 TypeScript 的哲学异曲同工:动态的能力之上再加一层显式契约。

3.2 没有 Schema,不等于没有约束

字段结构是自由的,但以下约束依然存在且比 MySQL 更需要主动设计:

  1. 唯一性靠索引而非主键约束:除了 _id 自动唯一,其他字段想唯一(如用户名)必须显式建唯一索引;
  2. 引用完整性没有外键保证:A 文档引用 B 的 _id,删掉 B 时数据库不会拦截你,一致性要靠应用层或事务来维持;
  3. 类型错位只有运行时才发现:往 price 里写字符串不会报错,直到某天排序或聚合出错才暴露——这就是要用 Schema 校验的原因。

3.3 事务范围不一样

MySQL 的 InnoDB 把多表事务当作基本盘;MongoDB 则把单文档原子性当作基本盘——对一个文档内多个字段的修改天然是原子的,所以很多”需要事务”的场景在第 04 篇就被建模消解掉了。真正跨文档、跨集合的事务由 4.0 引入的多文档事务机制兜底,可用但有成本与前提(副本集环境、时长上限),第 08 篇专门展开。

四、三方对照:MySQL、MongoDB 与 Elasticsearch 的选型边界

词汇表对齐之后,最后一步是把三者放进同一张地图,确认各自的位置。

图3:MySQL、MongoDB 与 Elasticsearch 的能力维度对比

维度 MySQL MongoDB Elasticsearch
数据模型 行式表,Schema 先行 BSON 文档,动态 Schema 倒排索引之上的 JSON 文档
核心强项 强事务、复杂关联查询 灵活建模、高写入、分布式内建 全文检索、相关性排序、聚合分析
事务 多语句事务是基本盘 单文档原子;4.0+ 跨文档事务 无事务概念
写入可见性 提交即可见 提交即可见 近实时(默认约 1 秒 refresh)
水平扩展 依靠分库分表等外挂方案 原生分片集群 原生分片 + 副本
典型角色 业务事实来源 灵活业务的现代主存储 搜索与分析读模型

从这张表可以提炼出一套务实的组合拳:

  • 稳定核心走 MySQL:账户、余额、订单状态流转这些”错一分钱都是事故”的路径,享受成熟的事务与生态;
  • 形态多变、读取密集的业务数据走 MongoDB:商品详情、内容系统、用户行为流水,既免去频繁 ALTER TABLE,又获得原生的水平扩展路径;
  • 搜索体验交给 ES:把需要全文检索与筛选聚合的字段同步过去做成读模型,别逼着主存储去干搜索引擎的活。

三者不是升级替代的关系,而是各管一段。实际项目里最常见的架构,就是这三层各司其职的组合。

五、什么场景值得引入,哪些红线要想清楚

5.1 MongoDB 的舒适区

场景 为什么合适
商品目录 / 内容管理系统 属性因品类而异,嵌套描述一处取齐
用户画像 / 配置中心 字段持续演进,Schema 变更零成本
行为日志 / IoT 数据 写入吞吐高,配合桶模式做时序压缩
移动端 / 小程序后端 接口输出就是 JSON,前后端心智一致
快速迭代的原型 先跑起来再固化结构,试错成本低
需要平滑水平扩展的业务 分片集群内置,不必先分库分表

5.2 这些情况请三思

  1. 核心账务:高频跨实体强事务、严格的资金一致性审计,InnoDB 至今仍是更省心的选择;
  2. 重即席多表 JOIN 的报表:大量异构表临时关联的分析需求,关系库或 OLAP 引擎的表达力更强;
  3. 强约束合规场景:依赖数据库外键、CHECK 约束兜底数据正确性,而不是靠应用层纪律的团队,迁移前要先补齐这层能力;
  4. 只因为”听说更快”:任何存储换掉索引设计和访问模式谈性能都是空话,第 07 篇会用执行计划说话。

一条纪律:决定用 MongoDB 的理由应该是”我的数据形态适合文档模型”,而不是”MySQL 用腻了”。前者是选型,后者只会把碰巧合适的方案安在不合适的问题上。

六、总结与下一步

  • MongoDB 是通用文档数据库,与 KV、宽列、图型 NoSQL 同属一族但定位不同:它具备承担业务主存储的完整能力;
  • 磁盘格式是 BSON,在 JSON 之上补齐了 Decimal128、Date、ObjectId 等类型;金额用 Decimal128,超大文件另找 GridFS;
  • 从 MySQL 迁移认知时盯住三个”不能画等号”:集合无强制 Schema(用校验器补纪律)、除 _id 外的唯一性和引用完整性都要自己设计、事务基本盘是单文档原子性;
  • 与 MySQL、ES 是分工而非替代:MySQL 管强事务事实,MongoDB 管灵活业务数据,ES 管检索读模型;
  • ObjectId 天然趋势递增——好用,但当分片键会有热点问题,伏笔留给第 10 篇。

下一篇:《MongoDB 从零到一(02):Docker 环境搭建与 mongosh 快速上手》——用 Docker Compose 把 MongoDB 跑起来,安装 Compass 可视化工具,并把本文的示例数据亲手写进库里。

思考与练习

  1. 找一张你项目中字段最多的表,逐列判断:哪些属于”实体的固有属性”,哪些只是”某一小类的扩展属性”?后者换成文档模型后会落在哪里?
  2. 本文说”引用完整性没有外键保证”,试着列出你的业务里所有依赖外键拦截脏数据的场景,想一想换成 MongoDB 后分别由谁来兜底?
  3. price 字段为什么坚持用 NumberDecimal("4999.00") 而不是裸写 4999.00?动手在 JS 里执行一下 0.1 + 0.2 再回来想想。
上一页
2026-10-12 09:40:00
下一页