mongodb 中必须了解的关于 ObjectId 的知识

image-20220426140319090

ObjectId 的构成

ObjectId 的值由 12 个字节组成,其中:

  • 4个字节表示时间戳(自 Unix 纪元以来的秒数),记录创建时间;
  • 3个字节表示机器标识符,保证不同主机产生不同的 ObjectId 值;
  • 2个字节表示进程 ID,保证在同一台主机不同 MongoDB 进程产生不同的 ObjectId 值;
  • 3个字节表示自增计数器(以随机值开头),保证同一主机同一进程同一秒内产生 ObjectId 的唯一性。

ObjectId = 时间戳(4字节) + 机器标识码(3字节) + 进程 ID(2字节) + 计数器(3字节)

1 2 3 4 5 6 7 8 9 10 11 12
时间戳 机器标识码 进程 ID 计数器
机器随机数

每个字节的值为 1~254,ObjectId 转换成字符串后,是用十六进制表示,所以,字符串型 ObjectId 有 24 个字符。

ObjectId 的值无法保证生成顺序

ObjectId 前 4 个字节存的是时间戳,而时间是递增的,所以 ObjectId 总体保证递增的顺序。

存储的时间戳只精确到秒,在同一台机器不同的 MongoDB 进程,同一秒内生成的 ObjectId,进程 ID 小的会排在大的前面。存在这种情况,进程 ID 大 的先生成 ObjectId,但还是会排在进程 ID 小的后面。所以 ObjectId 递增不是绝对的。

ObjectId 在一秒内生成的数量上限

3 个字节所能表达的最大的整数:\(2^{24}-1\)。所以一个 MongoDB 进程,在一秒内最多能生成 \(2^{24}-1\) 个ObjectId。

从目前机器的性能来看,要超过这个限制几乎是不可能的。

ObjectId 的唯一性

ObjectId 近似唯一,理论上会出现很小概率 \(\frac{1}{2^{24}-1}\) 的重复情况,这取决于 MongoDB 驱动实现 ObjectId 方式。

以 C# 官方驱动来说,构成 ObjectId 的计数器,C# 使用了 Interlocked.Increment 实现,保证了同一MongoDB 进程在同一秒内生成的多个 ObjectId 的计数器是累加的,从而保证了生成的 ObjectId 是唯一的。

不过,有些版本的驱动是使用了随机数作为计数器,这种情况下并不能保证生成的 ObjectId 是唯一的。

所以,除非你使用的是一个非常老的版本,或者很小众的驱动,否则都不需要为重复的 ObjectId 担心。

可以使用表达查询 ObjectId

1
db.comments.find({_id: {$gt: ObjectId("5272e0f00000000000000000")}})

ObjectId 无法修改

文档一旦写入到集合中,则其 _id 将不允许改变。如果需要更改某个文档的 _id,可以将文档删除后重新添加到集合中。

参考

  1. https://www.jianshu.com/p/7c4bfa516acf
  2. ObjectId — MongoDB Manual
  3. MongoDB ObjectId to Timestamp Converter