MongoDB自然排序


名词解释

natural order
    该排序下数据库参照文档在磁盘上的存储顺序。这是默认的排序顺序。

ObjectId
    一个特定的12字节BSON类型,用于保证集合中的唯一性。ObjectId基于timestamp、machine ID、process ID和 a process-local incremental counter产生。MongoDB使用ObjectId值作为_id键的默认值。


_id 和 ObjectId

当集合创建的时候,MongoDB在_id键上创建了一个唯一索引。这个索引阻止插入在_id键上有相同值的两个文档。你不能删除_id键上的索引。

集合里的文档必需有一个_id键,这个键的值可以是任意类型的,默认是ObjectId对象。在一个集合里面,每个文档都有唯一的"_id" 值,来确保集合里面每个文档都能被唯一标识。如果有两个集合的话,两个集合可以都有一个值为123 的"_id" 键,但是每个集合里面只能有一个"_id" 是123 的文档。

1. ObjectId


ObjectId 是"_id" 的默认类型。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。这是MongoDB 采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键值既费力还费时。MongoDB 从一开始就设计用来作为分布式数据库,处理多个节点是一个核心要求。后面会看到ObjectId 类型在分片环境中要容易生成得多。

ObjectId 使用12 字节的存储空间,每个字节两位十六进制数字,是一个24 位的字符串。由于看起来很长,不少人会觉得难以处理。但关键是要知道这个长长的ObjectId 是实际存储数据的两倍长。

如果快速连续创建多个ObjectId,会发现每次只有最后几位数字有变化。另外中间的几位数字也会变化(要
是在创建的过程中停顿几秒钟)。这是ObjectId的创建方式导致的。12字节按照如下方式生成:
      0|1|2|3 | 4|5|6 | 7|8 | 9|10|11
      时间戳 | 机器  | PID | 计数器
前4字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。

时间戳,与随后的5个字节组合起来,提供了秒级别的唯一性。
由于时间戳在前,这意味着ObjectId大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提
高效率,但是这个是没有保证的,仅仅是"大致"。这4个字节也隐含了文档创建的时间。绝大多数驱动都会公开
一个方法从ObjectId获取这个信息。

因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个没有必要,因为时间戳的实际值并不
重要,只要其总是不停增加就好了(每秒一次)。

接下来的三个字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的
ObjectId,不产生冲突。

为了确保在同一台机器上并发的多个进程产生的ObjectId是唯一的。后3个字节就是一个自动增加的计数器,确
保相同进程同一秒产生的ObjectId也是不一样的。同一秒钟最多允许每个进程拥有256(16777216)个不同的ObjectId。

2. 自动生成_id


前面讲到,如果插入文档的时候没有"_id" 键,系统会自动帮你创建一个。可以由MongoDB 服务器来做这件事情,但通常会在客户端由驱动程序完成。理由如下。

虽然ObjectId 设计成轻量型的,易于生成,但是毕竟生成的时候还是产生开销。在客户端生成体现了MongoDB 的设计理念:能从服务器端转移到驱动程序来做的事,就尽量转移。这种理念背后的原因是,即便是像MongoDB 这样的可扩展数据库,扩展应用层也要比扩展数据库层容易得多。将事务交由客户端来处理,就减轻了数据库扩展的负担。

在客户端生成ObjectId,驱动程序能够提供更加丰富的API。例如,驱动程序可以有自己的insert 方法,可以返回生成的ObjectId,也可以直接将其插入文档。如果驱动程序允许服务器生成ObjectId,那么将需要单独的查询,以确定插入的文档中的"_id" 值。



自然排序

1. _id和$natural

那么_id排序可以理解为相当于是按插入时间排序,因为它由客户端驱动产生并内嵌有当前时间戳。按$natural排序的时候,相当于是按数据在磁盘上的组织顺序排序。

2. 索引使用

包含$natural排序的查询不使用索引满足查询谓词,但有个例外:如果查询谓词是_id键上的等值条件},那么使用$natural排序的查询可以使用_id索引。

3. 自然排序内幕

MongoDB的查询排序默认是未定义的,因此会返回文档的顺序。如果没有查询条件,那么将使用自然顺序(natural order)。结果以它们被找到的顺序来返回,可能与插入顺序(但不能保证)或者索引使用的顺序一致。

一些会影响存储(自然)顺序的情况:
. 如果文档被更新而无法放置在它们当前分配的空间,那么会被移动
. 新的文档可能会插入到因删除或移动文档而产生的可用空隙空间

如果使用了索引,文档将以通过索引被找到的顺序返回。如果使用了不止一个索引,那么顺序内部依赖于在重复数据删除(de-duplication)过程中首先通过哪个索引识别文档。

如果你需要一个特定顺序,那么你必需在你的查询中包含一个排序。

需要指出的特例是固定集合的自然顺序(Capped Collections' natural order),因为文档不能移动且以插入的顺序存储。排序是固定集合特性的部分,确保最旧的文档先删除掉。另外,在一个固定集合里文档不能被删除或移动。

说明影响自然顺序的情况:
如果文档被移动、删除,你可能得到不同的结果集。如果没有文档插入、更新、删除你会获得相同的结果。添加索引不会影响文档在磁盘上的位置。

还要附加说明的是,如果你使用复制,在复制集成员之间自然顺序可能不一样。



参考资料

$natural
https://docs.mongodb.com/manual/reference/operator/meta/natural/

cursor.sort()
https://docs.mongodb.com/manual/reference/method/cursor.sort/#return-natural-order

Capped Collections
https://docs.mongodb.com/manual/core/capped-collections/

Default _id Index
https://docs.mongodb.com/manual/indexes/#default-id-index

重复数据删除(De-duplication)技术研究
http://blog.csdn.net/liuaigui/article/details/5829083