1. 程式人生 > >【知識小課堂】 mongodb 之 objectId

【知識小課堂】 mongodb 之 objectId

 一、OBJECTID

因公司開發人員在使用MONGODB時,總遇到一些小問題。為了增加大家的mongodb 資料庫知識。
決定每週進行一、兩次的知識小課堂。這裡把內容整理出來,上傳到部落格中。也算是自己的一個mongodb 歷程吧

1.1 結構


ObjectId("52cbab42231dea1e819b2a37"),
ObjectId("52cbab5b231dea1e819b2a38"),
ObjectId("52cbab70231dea1e819b2a39"),

52cbab70      時間戳
231dea        機器號
1e81        程序ID
9b2a39    自增數



以上數字為 16進製表示

特點:


1.24個16進位制資料,使用 12位元組的儲存空間。

2.最後3個位元組為:自動增長。可確保每秒生成的值也不一樣,一秒最多允許每個程序擁有256^3個不同ObjectId

(後面3位16進位制存在量可以達到: 16,777,216
也可以從儲存量計算出來 FFFFFF 轉換成10進位制就是16,777,215)


3.可轉移到客戶端生成,而減輕伺服器負擔(需要客戶端的驅動程式)


下面介紹幾個相關的函式

>> x=ObjectId()
ObjectId("53b3a89bf988c39955a30f9e")
> x
ObjectId("53b3a89bf988c39955a30f9e")
>x.str
53b3a89bf988c39955a30f9e

> x.toString()
ObjectId("53b3a89bf988c39955a30f9e")
> x.getTimestamp()
ISODate("2014-07-02T06:37:15Z")
> x.valueOf()
53b3a89bf988c39955a30f9e
>

函式說明:

1.Str 功能 與valueOf()相同

2.toString()  把一個物件轉換成

了一字串。這時兩個objectId可以對比。

3.getTimestamp() 抽取 時間

我們再來看看一個有意思的查詢:

既然可以把從objectId 獲取日期,那我是否可以使用一個日期做為條件,使用_id 欄位來進行查詢呢。

我的方法是 

 使用 函式  getTimestamp()先把欄位 進行處理,這在一般的關係型資料庫非常常用。
<span style="font-size:18px;">> a = db.order.findOne()
{
	"_id" : ObjectId("5331128631a4804b226471e4"),
	"md5" : "cacae8722c325d62e795e5c273d5f49b",
                   ……
}
> a."_id"
Wed Jul  2 14:50:57.494 SyntaxError: Unexpected string
> a._id.getTimestamp()
ISODate("2014-03-25T05:22:14Z")
> new Date("2014,03,25")
ISODate("2014-03-24T16:00:00Z")
> new Date("2014,03,25")
ISODate("2014-03-24T16:00:00Z")
> db.order.find({"_id.getTimestamp()":{$gt:new Date("2014,03,25")}}).count()
0
> db.order.find({_id.getTimestamp():{$gt:new Date("2014,03,25")}}).count()
Wed Jul  2 14:54:00.298 SyntaxError: Unexpected token .
> 

</span>


可以看到,看來是行不通。mongodb 無法識別 處理後的欄位。

那隻能自己生成一個objectId 再進行對比了。

我以前一直以為是無法進行兩個 objectId 來進行對比的。

但查詢了官方資料。並沒有看到相關按時間生成新的objectID 的方法/函式。

(http://docs.mongodb.org/manual/reference/method/)

後來找到了開發牛人的一個便方,解決了此問題。程式碼如下:

<span style="font-size:18px;">#構建一個指定日期的objectId()
> var timestamp = Math.floor(new Date(2014,03,01).getTime() / 1000);  #getTime() 返回毫秒數
> var hex       = (‘00000000’ + timestamp.toString(16)).substr(-8); #前填充0
> var v_objectId  = new ObjectId(hex + new ObjectId().str.substring(8)); #更換掉前面的時間值
</span>

其它方法也比較簡單,就是把一個日期轉換成16進位制後,替換到一個新生成的objectId 中去。


當自己構造好一個objectId後,後面我們來使用它進行查詢:

<span style="font-size:18px;">#用objectId() 來進行對比查詢
> db.order.find({_id:{$gt:v_objectId}})
{ "_id" : ObjectId("533e1049e4271af009000005"), "md5" : "a99038f392284b7dac895dc0030486c8",
...
 }
</span>

可以看到,查詢結果出來了。從上面可以看出。兩個objectId 是可以進行對比的。

從上面示例程式碼看到

雖然可以從ojbectId 中提取一些資訊。但如果要使用 objectid中的資訊來做為查詢條件,相對還是比較麻煩的。

就象我上面的,如果我要從objectID 的建立日期 來做為查詢條件,我自己得先用查詢時間構建一個objectID,然後再進行查詢。

如果查詢條件還有範圍什麼的。還不如再建立一個欄位create_dt 來儲存記錄的建立時間。以後查詢統計用此欄位,還更方便。

我們再來看看查詢的計劃,看到已使用了索引,此計劃雖然不能說明什麼。但也可以讓我知道是用_id 欄位的索引。

<span style="font-size:18px;">> db.order.find({_id:{$gt:v_objectId}}).explain()
{
	"cursor" : "BtreeCursor _id_",
	"isMultiKey" : false,
	"n" : 3,
	"nscannedObjects" : 3,
	"nscanned" : 3,
	"nscannedObjectsAllPlans" : 3,
	"nscannedAllPlans" : 3,
	"scanAndOrder" : false,
	"indexOnly" : false,
	"nYields" : 0,
	"nChunkSkips" : 0,
	"millis" : 0,
	"indexBounds" : {
		"_id" : [
			[
				ObjectId("533991005c1e4132ccbaf755"),
				ObjectId("ffffffffffffffffffffffff")
			]
		]
	},
	"server" : "localhost.localdomain:27017"
}
> 

</span>