← 返回题目列表

MongoDB 的 ObjectId 和 BSON 有什么特点?

高频 简单 第 1 / 31 题 更新于 2026/07/29
MongoDBObjectIdBSON文档模型

简化版

MongoDB 文档底层用 BSON 存储,BSON 是 JSON 的二进制扩展,支持 DateObjectId、二进制、不同数字类型等。ObjectId 通常作为 _id 默认值,它包含时间戳等信息,基本能保证分布式生成时的唯一性,但不是严格连续递增 ID。

详细版

ObjectId 是 MongoDB 最常见的主键类型,长度为 12 字节,通常展示成 24 位十六进制字符串。它的前 4 字节与生成时间有关,所以大体按时间递增,但不能把它当成业务序号或绝对时间排序依据。

BSON 是 MongoDB 的二进制文档格式,相比普通 JSON,它能保留更多类型信息,例如日期、长整型、Decimal128、二进制数据和 ObjectId。面试回答时要强调:MongoDB 看起来像存 JSON,实际存的是 BSON;_id 必须唯一,可以用默认 ObjectId,也可以由业务生成,但不要随意把业务含义塞进 _id

完整版教学

一、BSON 解决的是 JSON 类型不足的问题

很多人说 MongoDB 存 JSON,这句话方便理解,但不够准确。MongoDB 文档实际以 BSON 形式存储,BSON 可以理解为 Binary JSON。

普通 JSON 类型很少,主要是字符串、数字、布尔、数组、对象和 null。数据库系统还需要表达更多类型,比如日期、二进制、精确小数、不同位宽的整数,以及专门的对象标识。

类型JSON 是否天然支持BSON 常见表达面试关注点
日期Date避免把时间都当字符串
主键ObjectId默认 _id 常用类型
精确小数Decimal128金额不要随便用浮点
二进制BinData图片文件通常不直接塞大文档

记忆钩子:应用看见的是类 JSON 文档,MongoDB 落盘和网络传输里的核心格式是 BSON。

二、ObjectId 是默认主键,不是业务编号

MongoDB 每个文档都必须有 _id 字段,并且在一个集合内唯一。如果插入文档时没有提供 _id,驱动通常会自动生成 ObjectId。

ObjectId 常见展示形式如下:

{
  _id: ObjectId("65f1a2b3c4d5e6f789012345"),
  name: "Alice",
  createdAt: ISODate("2026-07-29T10:00:00Z")
}

它很适合做技术主键,因为生成成本低,不依赖中心化发号器,也能在多个客户端并发生成时保持极低冲突概率。

但它不适合做订单号、用户编号、流水号。业务编号通常要求可读、可校验、可按规则生成,ObjectId 不承担这些业务语义。

三、ObjectId 的 12 字节结构为什么常被追问

ObjectId 长度是 12 字节,通常编码成 24 个十六进制字符。可以按下面方式记:

ObjectId 12 bytes
4 bytes timestamp
+ 5 bytes process/random
+ 3 bytes counter
= 12 bytes

前 4 字节是秒级时间戳,所以 ObjectId 大体随时间增长。后面的随机或进程相关部分加计数器,用来降低不同机器、不同进程、同一秒内并发生成的冲突概率。

这也是为什么很多调试工具能从 ObjectId 里解析出生成时间。但注意它只有秒级时间,并且受客户端生成时间影响,不能替代严谨的 createdAt 字段。

四、_id 索引是天然存在的唯一索引

MongoDB 会为 _id 建唯一索引。查询单条文档时,按 _id 查通常是最高效路径之一。

db.users.findOne({ _id: ObjectId("65f1a2b3c4d5e6f789012345") })

如果把 _id 设计成很大的字符串或复杂对象,会直接影响索引体积、内存占用和写入成本。面试里可以补一句:_id 不只是字段,它还会进入唯一索引,所以主键类型会影响存储和性能。

常见工程选择:

主键方案优点风险
默认 ObjectId简单、分布式友好不适合业务可读编号
业务字符串可读、可对账索引更大,规则变更麻烦
雪花 ID趋势递增、跨库统一依赖发号算法和时钟处理

五、ObjectId 大体有序,但别夸大有序性

因为 ObjectId 前 4 字节是时间戳,同一集合中按 _id 排序,通常能得到大体按插入时间排列的结果。

db.logs.find().sort({ _id: -1 }).limit(20)

这个写法在简单日志列表里有时可用,但它不是严谨的业务排序。原因有 3 个:

  1. ObjectId 可以由客户端生成,客户端时间可能不准。
  2. 秒级时间戳不能区分同一秒内大量写入的真实先后。
  3. 导入历史数据或手动指定 _id 会破坏默认假设。

严谨做法是显式维护 createdAt,并配合 _id 做稳定排序:

db.logs.find().sort({ createdAt: -1, _id: -1 }).limit(20)

六、BSON 文档也有大小边界

MongoDB 单个 BSON 文档有大小限制,常见上限是 16MB。这个限制会影响建模:不能把无限增长的评论、日志、消息明细都一直嵌进一个文档。

如果一个用户文档里维护 orders: [],用户订单不断增加,文档会越来越大,更新数组也越来越重。更好的方式通常是把订单放到 orders 集合,用 userId 关联。

// 容易膨胀
{ _id: 1, orders: [ /* 可能无限增长 */ ] }

// 更可控
{ _id: 1001, userId: 1, amount: 99, createdAt: ISODate() }

面试里提到 BSON 时,不要只说“二进制 JSON”,还要能联系到类型表达、文档大小、索引大小和建模边界。

七、常见误区与追问

  • 误区:MongoDB 存的就是普通 JSON。 更准确说法是文档模型像 JSON,实际存储格式是 BSON,支持更多数据库类型。
  • 误区:ObjectId 是全局严格递增 ID。 它大体带时间趋势,但不是严格全局序列,也不适合当业务流水号。
  • 误区:有 ObjectId 就不需要 createdAt。 ObjectId 只能粗略反映生成时间,严谨查询、排序和审计仍建议单独存时间字段。
  • 追问:为什么 _id 类型会影响性能? _id 有唯一索引,字段越大,索引占用和比较成本越高。
  • 追问:ObjectId 能不能由业务自己生成? 可以,只要保证集合内唯一,但要权衡生成规则、索引体积和业务耦合。
  • 追问:BSON 的 16MB 限制会影响什么设计? 会影响无限增长数组、大文档、内嵌历史明细等建模方式。

八、加强记忆

记 MongoDB 的 ObjectId 和 BSON,可以串成“BSON 管类型,ObjectId 管默认身份”。BSON 让文档不止是普通 JSON,能表达日期、二进制、Decimal128 和 ObjectId;ObjectId 是 _id 的常见默认值,12 字节、24 位十六进制展示、前 4 字节带秒级时间,所以大体按时间增长。面试中要主动补边界:ObjectId 不是业务编号,不保证严格连续,不能替代 createdAt;BSON 文档也有大小限制,大数组和大对象要重新考虑集合拆分。