MongoDB權威指南(6)- 高級主題
1.數據庫命令
mongoDB提供了范圍廣泛的數據庫命令,覆蓋了除create,read,update,delete之外的所有功能。比如我們前邊用到的getLastError命令,用來查看update時受影響的數量。
命令是如何工作的
這里有個你比較熟悉的例子:drop,如果從shell里刪除一個collection,我們運行db.test.drop().實際上,在內部執行的是drop命令,跟下邊用runCommand執行的操作是一樣的
> db.runCommand({"drop" : "test"}); {"nIndexesWas" : 1,"msg" : "indexes dropped for collection","ns" : "test.test","ok" : true }
作為結果返回的document叫做命令響應,包括了一些信息,如命令執行是否成功等。
實際上,mongoDB的命令被實現為一種對叫$cmd的collection的特殊查詢,runCommand只是使用參數進行了一次查詢,所以我們的drop也可以這樣寫
db.$cmd.findOne({"drop" : "test"});
當mongoDB服務器接到一個對$cmd的查詢時,使用一種特殊的邏輯來處理。幾乎所有的驅動都提供了runCommand方法來執行命令,實際上這些命令都可以通過執行查詢的方式來完成。
命令參考
到目前mongoDB支持75個以上的數據庫命令,而且這些命令越來越多,你可以通過兩種方式查看支持的命令列表,一是在shell里執行 db.listCommands(),二是在瀏覽器里訪問mongoDB的管理站點http://localhost:28017/_commands
下邊列出了一些最常用的命令:
- buildInfo: {"buildInfo" : 1}, 返回mongoDB服務器版本和宿主操作系統的信息
- collStats: {"collStats" : collection},給出指定collection的統計信息,包括數據大小,分配的存儲控件,索引大小等
- distinct: {"distinct" : collection, "key": key, "query": query} 返回在指定的collection里符合query條件的所有key的值
- drop: {"drop" : collection}, 刪除collection的說有數據
- dropDatabase: {"dropDatabase" : 1}, 刪除當前數據庫的所有數據
- dropIndexes: {"dropIndexes" : collection, "index" : name}, 刪除collection上名字為name的索引
- findAndModify:參見第3章
- getLastError: {"getLastError" : 1[, "w" : w[, "wtimeout" : timeout]]}, 檢查此連接上最后操作的錯誤或狀態信息,可以指定一個選項,此命令將會阻塞直到w個salves復制了最后的那個操作或者時間超時(毫秒)
- isMaster: {"isMaster" : 1}, 檢查此服務器是master還是slave
- listCommands: {"listCommands" : 1}, 列出此服務器上所有可用命令
- listDatabases: {"listDatabases" : 1},列出服務器上所有數據庫
- ping: {"ping" : 1},檢查服務器是否正在運行,即使服務器處于鎖定狀體此命令也會立即返回
- renameCollection: {"renameCollection" : a, "to" : b}, 將collection的名字從a改為b
- repairDatabase:{"repairDatabase" : 1}, 修復并壓縮當前數據庫
- serverStatus:{"serverStatus" : 1}, 獲取此服務器的管理統計信息
2.Capped Collections
mongoDB還支持一種特殊的collection叫Capped Collections,這種collection是事先創建并且是大小固定的。固定大小就帶來了一個問題,如果collection滿了之后對其執行插入該如何處理。Capped Collection的工作方式像一個循環隊列,如果空間用完了,那么最舊的數據會被刪除掉,新數據會取代舊數據的位置。這就是說,隨著新 document的插入,最舊的document會自動消亡。
某些操作在Capped Collection上是不允許執行的。不允許執行document刪除,也不允許執行會導致document移動的更新(使document變大的更新),這樣就可以保證Capped Collections里的document是按照插入順序存儲的。
特性和用例
Capped Collection的這些特點和限制會產生有趣的特性。首先是插入很快,執行插入的時候不再需要分配空間,服務器就不用查詢空閑列表,插入的 document直接放在collection的尾部。缺省情況下,也不會更新索引,所以一次插入本質上就是一次內存拷貝。另外一個特性是,按插入順序查詢很快,document是按照插入順序存儲的,只需要按它們在磁盤上的順序遍歷。最后,舊數據的自動消亡也是個有用的特性。包括了上述3個特性的 Capped Collection對某些用例特別適用,如日志,實際上Capped Collection原本就是設計來存儲內部復制日志的。另外還有個好的用例,緩存小規模的document。
創建Capped Collections
> db.createCollection("my_collection", {capped: true, size: 100000}); { "ok" : true }
另外還可以指定document的數量上限,如下
> db.createCollection("my_collection", {capped: true, size: 100000, max: 100}); { "ok" : true }
將一個普通collection轉化為Capped Collection
> db.runCommand({convertToCapped: "test", size: 10000}); { "ok" : true }
自然排序
自然排序是Capped Collection上的一種特殊排序,就是按照磁盤上的存儲順序排序。當然也可以按反存儲順序排序,指定參數-1.
> db.my_collection.find().sort({"$natural" : -1})
循環游標(Tailable Cursors)
循環游標只能用于Capped Collection上,循環游標是一種特殊的持久性的游標,在結果集耗盡之后并不會關閉,當有了新document插入到collection之后,它可以繼續取出。shell不支持循環游標。
3.GridFS: 存儲文件
GridFS是mongoDB里的存儲二進制大文件的機制。使用GridFS最簡單的方式就是使用mongofiles工具,這個工具在分發包里邊有。下邊演示了如何使用mongofiles上傳下載列出文件。
$ echo "Hello, world" > foo.txt $ ./mongofiles put foo.txt connected to: 127.0.0.1 added file: { _id: ObjectId('4c0d2a6c3052c25545139b88'), filename: "foo.txt", length: 13, chunkSize: 262144, uploadDate: new Date(1275931244818), md5: "a7966bf58e23583c9a5a4059383ff850" } done! $ ./mongofiles list connected to: 127.0.0.1 foo.txt 13 $ rm foo.txt $ ./mongofiles get foo.txt connected to: 127.0.0.1 done write to: foo.txt $ cat foo.txt Hello, world
ps:如果你使用的是windows控制臺,需要把rm命令換成del,把cat命令換成type
mongofiles還支持search和delete。
GridFS是一個基于普通document之上的文件存儲規范,mongoDB服務器對GridFS請求幾乎沒有做任何特殊處理,所有的工作都是客戶端驅動和工具完成的。
GridFS的基本思想是將大文件分割成多個塊,每個塊存儲為一個document,除了存儲文件的每個塊外,還有個單獨的document把這些塊組織在一起,并且包含了文件的元數據信息。
GridFS中的塊有自己的collection,缺省是使用fs.chunks,文件元數據存儲在另外一個collection,缺省為fs.files。
4.服務器端腳本
db.eval函數允許你在服務器端執行javascript代碼,將javascript字符串傳遞給服務器,然后返回結果。
db.eval可以用來模擬事務,db.eval會鎖定數據庫,執行javascript,然后解鎖數據庫。雖然沒有回滾,這也能夠讓你保證一組操作是按順序執行。
代碼可以包裝進一個function發送也可以不包裝,下邊兩行是等價的
> db.eval("return 1;")1 > db.eval("function() { return 1; }")1
如果要傳遞參數的話,就必須將代碼封裝進函數了
> db.eval("function(x,y,z) { return x + y + z; }", [num1, num2, num3])
Stored Javascript
mongoDB里的每個數據庫都有個特殊的collection叫system.js,用于存儲javascript變量,這些變量可以在任意的 javascript上下文中使用,包括"$where"子句、db.eval調用、MapReduce任務等。通過簡單的插入就可以添加 javascript變量了。
> db.system.js.insert({"_id" : "x", "value" : 1})> db.system.js.insert({"_id" : "y", "value" : 2})> db.system.js.insert({"_id" : "z", "value" : 3})
這樣就在全局范圍定義了x、y、z三個變量,如果你想求它們的和
> db.eval("return x+y+z;")6
system.js同樣可以存儲javascript代碼,例如,你想創建一個日志打印函數
> db.system.js.insert({"_id" : "log", "value" : ... function(msg, level) { ... var levels = ["DEBUG", "WARN", "ERROR", "FATAL"]; ... level = level ? level : 0; // check if level is defined ... var now = new Date(); ... print(now + " " + levels[level] + msg); ... }})
那么你就可以在任何javascript上下文中調用此函數了
> db.eval("x = 1; log('x is '+x); x = 2; log('x is greater than 1', 1);");
數據庫日志中就會包含下邊的內容
Fri Jun 11 2010 11:12:39 GMT-0400 (EST) DEBUG x is 1
Fri Jun 11 2010 11:12:40 GMT-0400 (EST) WARN x is greater than 1
5.數據庫引用(Database References/DBRef)
一個DBRef就是一個嵌入的document,和其他的嵌入document一樣,但是必須有一些特殊的key。如下
{"$ref" : collection, "$id" : id_value}
這個DBRef引用了collection和id_value,使用這兩個值就可以唯一確定一個document。如果要引用其他數據庫的document,還需要第三個key,即"$db"
{"$ref" : collection, "$id" : id_value, "$db" : database}
Note:DBRef里邊的key的順序是有影響的,第一個必須是"$ref",然后是"$id",然后是可選的"$db"。
由于mongoDB是非關系型的(無join),document之間的引用(外鍵)通常是客戶端通過額外的查詢來完成的。mongoDB里有兩種常用的做法,一是簡單的手動引用,另外就是DBRef標準,很多驅動都是支持DBRef的。
簡單的手動引用
一般來說,手動寫代碼來處理引用的辦法用起來還是不錯的,我們只需要把另外的document的_id的值存起來,然后進行查詢,如:
> // grab a random blog post: > p = db.postings.findOne(); { "_id" : ObjectId("4b866f08234ae01d21d89604"), "author" : "jim", "title" : "Brewing Methods" }> // get more info on author of post p > a = db.users.findOne( { _id : p.author } ) { "_id" : "jim", "email" : "jim@gmail.com" }> // inverse: given an author, find all blog posts for the author > db.postings.find( {author : a._id } )
DBRef
各個驅動對DBRef的實現不太一樣,不是所有的驅動都把DBRef當成普通的嵌入document,有些提供了一個特殊類型來自動轉換。C#中使用一個叫DBRef的類,構造函數有兩個參數,collection的名字和_id,然后就可以用FollowReference方法來獲取引用的 document。
在javascript(shell)中,如下例:
> x = { name : 'Biology' } { "name" : "Biology" }> db.courses.save(x)> x { "name" : "Biology", "_id" : ObjectId("4b0552b0f0da7d1eb6f126a1") }> stu = { name : 'Joe', classes : [ new DBRef('courses', x._id) ] }// or we could write: // stu = { name : 'Joe', classes : [ {$ref:'courses',$id:x._id} ] } > db.students.save(stu)> stu { "name" : "Joe", "classes" : [ { "$ref" : "courses", "$id" : ObjectId("4b0552b0f0da7d1eb6f126a1") } ], "_id" : ObjectId("4b0552e4f0da7d1eb6f126a2") }> stu.classes[0] { "$ref" : "courses", "$id" : ObjectId("4b0552b0f0da7d1eb6f126a1") }> stu.classes[0].fetch() { "_id" : ObjectId("4b0552b0f0da7d1eb6f126a1"), "name" : "Biology" }>