MongoDB 進階


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"作為引用來使用,這樣更簡單,也更容易操作.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM