MongoDB中我们经常会接触到一个自动生成的字段:”_id”,类型为ObjectId。
ObjectId构成
之前我们使用MySQL等关系型数据库时,主键都是设置成自增的。
但在分布式环境下,这种方法就不可行了,会产生冲突。
为此,MongoDB采用了一个称之为ObjectId的类型来做主键。
ObjectId是一个12字节的 BSON类型字符串。按照字节顺序,依次代表:
4字节:UNIX时间戳
3字节:机器识别码
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值
- 如:
4df2dcec2cdcd20936a8b817
TimeStamp
前 4位是一个unix的时间戳,是一个int类别,我们将上面中的objectid的前4位进行提取“4df2dcec”,
然后再将他们安装十六进制 专为十进制:“1307761900”,这个数字就是一个时间戳,为了让效果更佳明显,
我们将这个时间戳转换成我们习惯的时间格式(精确到秒)
$ date -d '1970-01-01 UTC 1307761900 sec' -u
2011年 06月 11日 星期六 03:11:40UTC
前 4个字节其实隐藏了文档创建的时间,并且时间戳处在于字符的最前面,
这就意味着ObjectId大致会按照插入进行排序,这对于某些方面起到很大作用,
如作为索引提高搜索效率等等。使用时间戳还有一个好处是,某些客户端驱动可以通过ObjectId解析出该记录是何时插入的,
这也解答了我们平时快速连续创 建多个Objectid时,会发现前几位数字很少发现变化的现实,
因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个时间戳的真实值并 不重要,只要其总不停增加就好。
Machine
接下来的三个字节,就是 2cdcd2 ,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,
这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,
这也就是在同一台机器生成的objectid中间的字符串都是一模一样的原因。
pid
上面的Machine是为了确保在不同机器产生的objectid不冲突,
而pid就是为了在同一台机器不同的mongodb进程产生了objectid不冲突,接下来的0936两位就是产生objectid的进程标识符。
increment
前面的九个字节是保证了一秒内不同机器不同进程生成objectid不冲突,这后面的三个字节a8b817,
是一个自动增加的计数器,用来确保在同一秒内产生的objectid也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。
MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。
在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。
MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,
因为在多个 服务器上同步自动增加主键值既费力还费时。
补充:1个字节占多少个16进制位
1个字节是8位,最多表示0到256
而一位16最多只表示到16,即F表示16,要表示到256,就还需要第二位,
所以1个字节占2个16进制位
一个16进制位占0.5个字节
创建新的ObjectId
使用以下代码生成新的ObjectId:
>newObjectId = ObjectId()
上面的语句返回以下唯一生成的id:
ObjectId("5349b4ddd2781d08c09890f3")
你也可以使用生成的id来取代MongoDB自动生成的ObjectId:
>myObjectId = ObjectId("5349b4ddd2781d08c09890f4")
创建文档的时间戳
由于 ObjectId 中存储了 4 个字节的时间戳,所以你不需要为你的文档保存时间戳字段,
你可以通过 getTimestamp 函数来获取文档的创建时间:
>ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()
以上代码将返回 ISO 格式的文档创建时间:
ISODate("2014-04-12T21:49:17Z")
根据时间构造ObjectId
// 使用Date的字符串构造方法生成日期
// 然后使用Date对象的getTime获取毫秒数,再除以1000得到标准时间戳
> a = newDate("2012-12-12 00:00:00").getTime()/1000
1355241600
// 获取时间戳的标准十六进制表示
> a = a.toString(16)
50c75880
// 在后面填补16个0
> a = a + newArray(17).join("0")
50c758800000000000000000
// 使用24个字符串构造ObjectId
> b = newObjectId(a)
ObjectId("50c758800000000000000000")
// 获取时间以验证
> b.getTimestamp()
ISODate("2012-12-11T16:00:00Z")
上述过程中 newArray(17).join(“0″)目的是生成16个0拼接的字符串。
这里使用了点小技巧。
newArray(17)构造了一个17个元素的数组,但是数组里面没有元素,
join(atr)方法的作用是连接数组元素并且以其参数分割。17个元素正好有16个间隔,
所以最终拼接起来的字符串为16个。
ObjectId转换为字符串
在某些情况下,您可能需要将ObjectId转换为字符串格式。你可以使用下面的代码:
>newObjectId().str
以上代码将返回Guid格式的字符串::
5349b4ddd2781d08c09890f3
根据ObjectId按照插入时间排序
MongoDB默认在ObjectId上建立索引,是按照插入时间排序的。
我们可以使用此索引进行查询和排序。
// 按序插入三个文档
> db.col.insert({"num":1})
> db.col.insert({"num":2})
> db.col.insert({"num":3})
> db.col.find().pretty()
{ "_id" : ObjectId("53102fb4bf1044ed8b0ba36c"), "num" : 1 }
{ "_id" : ObjectId("53102fb9bf1044ed8b0ba36d"), "num" : 2 }
{ "_id" : ObjectId("53102fbabf1044ed8b0ba36e"), "num" : 3 }
// 按照_id升序,即按照插入时间升序
> db.col.find().sort({"_id":1}).pretty()
{ "_id" : ObjectId("53102fb4bf1044ed8b0ba36c"), "num" : 1 }
{ "_id" : ObjectId("53102fb9bf1044ed8b0ba36d"), "num" : 2 }
{ "_id" : ObjectId("53102fbabf1044ed8b0ba36e"), "num" : 3 }
// 按照_id降序,即按照插入时间降序
> db.col.find().sort({"_id":-1}).pretty()
{ "_id" : ObjectId("53102fbabf1044ed8b0ba36e"), "num" : 3 }
{ "_id" : ObjectId("53102fb9bf1044ed8b0ba36d"), "num" : 2 }
{ "_id" : ObjectId("53102fb4bf1044ed8b0ba36c"), "num" : 1 }
// 抽取num = 2的ObjectId用来过滤
> num2 = ObjectId("53102fb9bf1044ed8b0ba36d")
ObjectId("53102fb9bf1044ed8b0ba36d")
// 找出插入时间在num2之后的数据
> db.col.find({ "_id":{$gt:num2}}).pretty()
{ "_id" : ObjectId("53102fbabf1044ed8b0ba36e"), "num" : 3 }
参考来源: http://blog.csdn.net/permike/article/details/51800948
参考来源: http://blog.csdn.net/xiamizy/article/details/41521025