1.數據庫命令
a.命令的工作原理
drop命令,在shell中刪除一個集合,執行db.refactor.drop().其實這個函數實際運行的是drop命令,
可以用runCommand來達到一樣的效果:
db.runCommand({"drop":"refactor"})
{
"nIndexesWas" : 1,
"msg" : "indexes dropped for collection",
"ns" : "test.refactor",
"ok" : 1
}
命令的響應是一個文檔,包含了命令是否執行成功,還可能有些其他的命令輸出的信息.命令的響應文檔
都有一個"ok"鍵.ok鍵的值為1表示執行成功,為0表示執行失敗,當為0時,會有一個"errmsg"鍵,
它的值表示命令失敗的原因.
MongoDB中的命令其實是作為一種特殊類型的查詢實現的,這些查詢針對$cmd集合來執行.runCommand
僅僅是接受命令文檔,執行等價查詢,因此drop調用實際上是這樣的:
db.$cmd.findOne({"drop":"refactor"})
當MongoDB服務器得到查詢$cmd集合的請求時,會啟動一套特殊的邏輯來處理,而不是交給普通的查詢代碼來執行.
幾乎所有MongoDB驅動程序都提供一個類似runCommand的幫助方法來執行命令,如果有必要,也可以使用一個
簡單查詢的方式來運行命令.
訪問有些命令需要有管理員權限,必須在admin數據庫里面運行.
b)要獲得所有命令的最新列表,可以在shell中運行db.listCommands(),
也可以使用http://localhost:28017/_commands
2.固定集合
MongoDB不僅支持普通集合,還支持 固定集合,固定集合要事先創建,而且大小固定.
固定集合像一個環形隊列,如果空間不足,最早的文檔就會被刪除,為新的文檔提供空間.
固定集合在新文檔插入的時候自動淘汰最早的文檔.
固定集合不能刪除文檔(自動淘汰文檔除外),更新(尺寸增大)將導致文檔移動.
固定集合中的文檔以插入的順序存儲,不用維護一個已刪除的文檔釋放空間列表.
固定集合默認情況下沒有索引,即便是"_id"上也沒有索引.
a.固定集合的屬性和用法
對固定集合插入的速度很快,做插入操作時,無需額外分配空間,服務器也不必查找空閑列表來放置文檔,
直接將文檔插入集合的末尾就行了,如果有必要就將舊的覆蓋.默認情況下插入也無需更新索引.
對固定集合按照插入順序輸出的查詢速度很快,以為文檔本身就是按照插入順序存儲的,按照這個
順序查詢就是遍歷一下,返回結果的順序就是文檔在磁盤上的順序.默認情況下,對固定集合進行查找都
會以插入的順序返回結果.
固定集合能在新數據插入時,自動淘汰最早的數據.插入快速,按照插入順序查詢也很快,自動淘汰,這幾個
屬性組合起來使得固定集合特別適合像日志這種應用場景.事實上,MongoDB中設計固定集合的目的
就是用來存儲內部的復制日志oplog.固定集合還有一個用法是緩存少量的文檔,一般來說,固定集合
使用與任何想要自動淘汰過期屬性的場景.
b.創建固定集合
固定集合必須要在使用前顯示的創建.
如:
db.createCollection("refactorCapped",{"capped":true,size:100000,max:100})
上面的命令創建了一個固定集合refactorCapped,大小是100000字節,最大的文檔數100.
當指定文檔數量的上限時,必須同時指定大小.淘汰機制只有在容量還沒有滿時才會依據文檔數量來工作.
要是容量滿了,淘汰機制則會依據容量來工作.
可以將普通集合轉化成固定集合,如將blog集合轉換成大小為10000字節的固定集合
db.runCommand({convertToCapped:"blog",size:10000})
c.自然排序
固定集合的排序方式叫做自然排序.自然排序就是文檔在磁盤上的順序.
因為固定集合的文檔總是按照插入的順序存儲的,自然順序就是這樣的.默認情況下,
查詢固定集合后就是按照插入的順序返回文檔.也可以使用自然排序按照反向插入的順序查詢
如
db.blog.find().sort({"$natural":-1})
3.GridFS
GridFS是一種在MongoDB中存儲大二進制文件的機制,使用GridFS存文件的原因:
GridFS可以直接利用已經建立的復制或分片機制,對文件存儲來說故障恢復和擴展都很容易
GridFS可以避免用於存儲用戶上傳內容的文件系統出現的某些問題,如GridFS在同一個目錄下放置大量的文件是沒有問題的.
GridFS不產生磁盤碎片,因為MongoDB分配數據文件空間時以2GB為一塊.
a.使用GridFS
最簡單使用GridFS的方法是利用mongofiles.mongofiles可以用來在GridFS中上傳,下載,列示,查找和刪除文件.
可以用 mongofiles --help獲得幫助
如:
b)內部原理
GridFS是一個建立在普通MongoDB文檔基礎上的輕量級文件存儲規范.MongoDB服務器實際上對GridFS請求和普通的
請求一樣,所有相關工作都由客戶端驅動或者工具來完成.
GridFS的基本思想是可以將大文件分成很多塊,每塊作為一個單獨的文檔存儲,這樣就能存儲大文件了.由於MongoDB
支持在文檔中存儲二進制數據,可以最大限度減小塊的存儲開銷.另外,除了存儲文件本身的快,還有一個單獨的文檔用來
存儲分塊的信息和文件的元數據.
GridFS的塊有個單獨的集合,默認情況下,塊將使用fs.chunks集合,如果需要可以覆蓋.這個塊集合里面文檔的結構很簡單
{
"_id":ObjectId("...."),
"n":0,
"data":BindData("..."),
"files_id":ObjectId("....")
}
和別的MongoDB文檔一樣,塊也有自己唯一的"_id".files_id鍵是包含這個塊元數據的文件文檔的"_id".
n表示塊編號,也就是這個塊在源文件中的順序編號,data包含組成文件塊的二進制數據.
文件的元數據放在另一個集合中,默認是fs.files.這里面的每個文檔代表GridFS中的一個文件,與文件相關的
自定義元數據也可以存在其中.除了用戶自定義的鍵,GridFS規范定義了一些鍵
_id
文件唯一的id,在塊中作為files_id鍵的值存儲
length
文件內容總的字節數
chunksize
每塊的大小,以字節為單位,默認是256k,必要時可以調整.
uploadDate
文件存入GridFS的時間戳
md5
文件內容的md5檢驗和,由服務端用filemd5命令生成的,用於計算上傳塊的md5檢驗和
也意味着用戶可以檢驗md5鍵這個值,確保文件正確上傳了.
db.fs.files.find()
4.服務器端腳本
在服務器端可以通過db.eval函數來執行javascript腳本,也可以把javascript腳本保存在數據庫中,然后
在別的數據庫命令中調用.
a. db.eval
利用db.eval函數可以在MongoDB服務器端執行javascript腳本.這個函數先將給定的javascript字符串傳遞給
MongoDB服務器,在服務器上執行,然后返回結果.
db.eval可以用來模擬多文檔事務:db.eval鎖住數據庫,然后執行javascript,再解鎖.雖然沒有內置的回滾機制,
但這能確保一系列操作按照指定的數序發生.
發送代碼有兩種方式,封裝一個函數或者不封裝,如:
db.eval("return 'refactor';")
db.eval("function(){return 'refactor';}")
只有傳遞參數的時候,才必須要封裝成一個函數.參數通過db.eval的第二個參數傳遞,要寫成一個數組的形式.
如:
db.eval("function(name){return 'hello,'+name;}",['refactor'])
若db.eval的表達式要是復雜的話,調試的辦法是將調試信息寫進數據庫的日志中
如:
db.eval("print('hello refactor')")
這樣在日志里就能找到hello refactor
b.存儲javascript
每個MongoDB的數據庫中都有個特殊的集合:system.js,用來存放javascript變量.這些變量可以在任何MongoDB的
javascript上下文中調用,包括"$where"子句,db.eval調用,MapReduce作業.用insert可以將變量存在system.js中
如:
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;")
system.js可以存放javascript代碼,這樣就可以很方便的自定義一些腳本,如用javascript寫一個日志函數,將其存放在
system.js中:
db.system.js.insert(
{
"_id":"log",
"value":function(msg,level)
{
var levels=["DEBUG","WARN","ERROR","PATAL"];
level=level?level:0;
var now= new Date();
print( now +" "+ levels[level]+msg);
}
}
)
調用:
db.eval("log('refactor bolg test',1)")
使用存儲的javascript缺點是代碼會與常規的源代碼控制脫離,會弄亂客戶端發送來的javascript.
最適合使用存儲javascript的情況就是程序中有個地方都要用到一個javascript函數,這樣要是更新的話,
只需更新這個函數而不必沒出都修改.要是javascript代碼很長又要繁瑣使用的話,也可以使用存儲javascript,
這樣村一次會節省不少王擴傳輸時間.
c.安全性
執行javascript代碼就要考慮MongoDB的安全性.
如:
>func="function(){print('hello,"+username+"!');}"
如果username是用戶自定義的,可以使用這樣的字符串"');db.dropDatabase();print('",
代碼就變成了這樣:
>func="function(){print('hello,');db.dropDatabase();print('!');}"
為了避免這種情況,要限定作用域.
絕大多數驅動程序都為傳遞給數據庫的代碼提供了一種特殊類型,這是因為代碼實際上可以看成是一個字符串和一個
作用域的組合.作用域是一個保存着變量名和值映射關系的文檔.當javascript函數執行的時候,這種映射就
構成了函數的局部作用域.
5.數據庫引用
DBRef就像url,唯一確定一個到文檔的引用.它自動加載文檔的方式就像網站中url通過鏈接自動加載web頁面一樣.
a.DBRef是什么
DBRef是一個內嵌文檔,DBRef有些必選鍵,如:
{"$ref":collectionName,"$id":id_value}
DBRef指向一個集合,還有一個id_value用來在集合里面根據"_id"確定唯一的文檔.這兩條信息可以使DBRef能
唯一標識MongoDB數據庫內的任何一個文檔.如想引用另一個數據庫的文檔,DBRef中有可選鍵"$db"
{"$ref":collectionName,"$id":id_value,"$db":database}//注意鍵的順序不能改變.
b.實例
兩個集合 users(用戶),notes(筆記),
用戶可以創建筆記,筆記可以引用用戶或者別的筆記.
db.users.insert({"_id":"refactor","displayName":"dis_refactor"})
db.users.insert({"_id":"refactor2","displayName":"dis_refactor2"})
db.notes.insert({"_id":2,"author":"refactor","text":"refactor in mongodb"})
db.notes.insert(
{
"_id":22,
"author":"refactor22",
"text":"...DBRef likes url",
"references":
[
{"$ref":"users","$id":"refactor"},
{"$ref":"notes","$id":2}
]
}
)
var note=db.notes.findOne({"_id":22});
note.references.forEach(
function(ref){
printjson(db[ref.$ref].findOne({"_id":ref.$id}));
});
c.什么時候使用DBRef
在MongoDB中表示這種對其他文檔的引用關系,並不是只有DBRef方式.
上面的例子就用了另外一種引用:每個note的author鍵僅存儲了author文檔的"_id"鍵,沒有必要用DBRef,因為已經
知道每個author就是users集合里面的一個文檔.這種引用在GridFS的塊文檔中"files_id"鍵僅僅就是對文檔"_id"的引用.
在保存引用的時候是選擇DBRef還是至存儲"_id"?
保存"_id"會更加緊湊,對開發者而言就很輕量.但是DBRef能夠引用任意集合(甚至是任意數據庫)的文檔,開發者
不必知道和記住被引用的文檔在哪些集合里面.驅動程序和一些工具對DBRef提供了額外的功能(如自動去引用).
總之,存儲一些對 不同 集合的 文檔的引用時,最好用DBRef.否則最好存儲"_id"作為引用來使用,這樣更簡單,也更容易操作.