MongoDB【快速入门】
?
1.MongDB 簡介
MongoDB(來自于英文單詞“Humongous”,中文含義為“龐大”)是可以應用于各種規模的企業、各個行業以及各類應用程序的開源數據庫。作為一個適用于敏捷開發的數據庫,MongoDB 的數據模式可以隨著應用程序的發展而靈活地更新。與此同時,它也為開發人員 提供了傳統數據庫的功能:二級索引,完整的查詢系統以及嚴格一致性等等。 MongoDB 能夠使企業更加具有敏捷性和可擴展性,各種規模的企業都可以通過使用 MongoDB 來創建新的應用,提高與客戶之間的工作效率,加快產品上市時間,以及降低企業成本。
MongoDB?是專為可擴展性,高性能和高可用性而設計的數據庫。它可以從單服務器部署擴展到大型、復雜的多數據中心架構。利用內存計算的優勢,MongoDB 能夠提供高性能的數據讀寫操作。 MongoDB 的本地復制和自動故障轉移功能使您的應用程序具有企業級的可靠性和操作靈活性。
以上內容摘自官網:
1.1 文檔型數據庫
簡而言之,MongoDB是一個免費開源跨平臺的 NoSQL 數據庫,與關系型數據庫不同,MongoDB 的數據以類似于 JSON 格式的二進制文檔存儲:
{name: "我沒有三顆心臟",age: 22, }文檔型的數據存儲方式有幾個重要好處:
1.2 MongoDB 基礎概念
可以使用我們熟悉的 MySQL 數據庫來加以對比:
| 數據庫(database) | 容器(database) |
| 表(table) | 集合(collection) |
| 行(row) | 文檔(document) |
| 列(column) | 域(filed) |
| 索引(index) | 索引(index) |
也借用一下菜鳥教程的圖來更加形象生動的說明一下:
?
這很容易理解,但是問題在于:我們為什么要引入新的概念呢?(也就是為什么我們要把“表”替換成“集合”,“行”替換成“文檔”,“列”替換成“域”呢?)原因在于,其實在?MySQL 這樣的典型關系型數據中,我們是在定義表的時候定義列的,但是由于上述文檔型數據庫的特點,它允許文檔的數據類型可以對應到語言的數據類型,所以我們是在定義文檔的時候才會定義域的。
也就是說,集合中的每個文檔都可以有獨立的域。因此,雖說集合相對于表來說是一個簡化了的容器,而文檔則包含了比行要多得多的信息。
2 搭建環境
怎么樣都好,搭建好環境就行,這里以 OS 環境為例,你可以使用 OSX 的 brew 安裝 mongodb:
brew install mongodb在運行之前我們需要創建一個數據庫存儲目錄?/data/db:
sudo mkdir -p /data/db然后啟動 mongodb,默認數據庫目錄即為?/data/db(如果不是,可以使用?--dbpath?指令來指定):
sudo mongd過一會兒你就能看到你的 mongodb 運行起來的提示:
?
具體的搭建過程可以參考菜鳥的教程:http://www.runoob.com/mongodb/mongodb-window-install.html
3 基于 Shell 的 CRUD
3.1 連接實例
通過上面的步驟我們在系統里運行了一個 mongodb 實例,接下來通過?mongo?命令來連接它:
mongo [options] [db address] [file names]由于上面運行的 mongodb 運行在 27017 端口,并且滅有啟動安全模式,所以我們也不需要輸入用戶名和密碼就可以直接連接:
mongo 127.0.0.1:27017或者通過?--host?和?--port?選項指定主機和端口。一切順利的話,就進入了 mongoDB?shell,shell?會報出一連串權限警告,不過不用擔心,這并不會影響之后的操作。在添加授權用戶和開啟認證后,這些警告會自動消失。
?
3.2 CRUD 操作
在進行增刪改查操作之前,我們需要先了解一下常用的?shell?命令:
- db?顯示當前所在數據庫,默認為?test
- show dbs?列出可用數據庫
- show tables?show collections?列出數據庫中可用集合
- use <database>?用于切換數據庫
mongoDB 預設有兩個數據庫,admin 和 local,admin 用來存放系統數據,local 用來存放該實例數據,在副本集中,一個實例的 local 數據庫對于其它實例是不可見的。使用 use 命令切換數據庫:
> use admin > use local > use newDatabase可以 use 一個不存在的數據庫,當你存入新數據時,mongoDB 會創建這個數據庫:
> use newDatabase > db.newCollection.insert({x:1}) WriteResult({ "nInserted" : 1 })以上命令向數據庫中插入一個文檔,返回 1 表示插入成功,mongoDB 自動創建 newCollection 集合和數據庫 newDatabase。下面將對增查改刪操作進行一個簡單的演示。
3.2.1 創建(Create)
MongoDB 提供?insert?方法創建新文檔:
- db.collection.inserOne()?插入單個文檔
WriteResult({ "nInserted" : 1 }) - db.collection.inserMany()?插入多個文檔
- db.collection.insert()?插入單條或多條文檔
我們接著在剛才新創建的 newDatabase 下面新增數據吧:
db.newCollection.insert({name:"wmyskxz",age:22})根據以往經驗應該會覺得蠻奇怪的,因為之前在這個集合中插入的數據格式是?{x:1}?的,而這里新增的數據格式確是?{name:"wmyskxz",age:22}?這個樣子的。還記得嗎,文檔型數據庫的與傳統型的關系型數據的區別就是在這里!
并且要注意,age:22?和?age:"22"?是不一樣的哦,前者插入的是一個數值,而后者是字符串,我們可以通過?db.newCollection.find()?命令查看到剛剛插入的文檔:
> db.newCollection.find() { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }這里有一個神奇的返回,那就是多了一個叫做?_id?的東西,這是 MongoDB 為你自動添加的字段,你也可以自己生成。大部分情況下還是會讓 MongoDB 為我們生成,而且默認情況下,該字段是被加上了索引的。
3.2.2 查找(Read)
MongoDB 提供?find?方法查找文檔,第一個參數為查詢條件:
> db.newCollection.find() # 查找所有文檔 { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 } > db.newCollection.find({name:"wmyskxz"}) # 查找 name 為 wmyskxz 的文檔 { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 } > db.newCollection.find({age:{$gt:20}}) # 查找 age 大于 20 的文檔 { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 }上述代碼中的$gt對應于大于號>的轉義。
第二個參數可以傳入投影文檔映射數據:
> db.newCollection.find({age:{$gt:20}},{name:1}) { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz" }上述命令將查找?age?大于 20 的文檔,返回?name?字段,排除其他字段。投影文檔中字段為 1 或其他真值表示包含,0 或假值表示排除,可以設置多個字段位為 1 或 0,但不能混合使用。
為了測試,我們為這個集合弄了一些奇奇怪怪的數據:
> db.newCollection.find() { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30 }然后再來測試:
> db.newCollection.find({age:{$gt:20}},{name:1,x:1}) { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz" } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "x" : 1 } > db.newCollection.find({age:{$gt:20}},{name:0,x:0}) { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "age" : 22 } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "age" : 22, "y" : 30 } > db.newCollection.find({age:{$gt:20}},{name:0,x:1}) Error: error: {"ok" : 0,"errmsg" : "Projection cannot have a mix of inclusion and exclusion.","code" : 2,"codeName" : "BadValue" }從上面的命令我們就可以把我們的一些想法和上面的結論得以驗證,perfect!
除此之外,還可以通過?count、skip、limit?等指針(Cursor)方法,改變文檔查詢的執行方式:
> db.newCollection.find().count() 3 > db.newCollection.find().skip(1).limit(10).sort({age:1}) { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 22 } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30 }上述查找命令跳過 1 個文檔,限制輸出 10 個,以?age?子段正序排序(大于 0 為正序,小于 0 位反序)輸出結果。最后,可以使用 Cursor 方法中的 pretty 方法,提升查詢文檔的易讀性,特別是在查看嵌套的文檔和配置文件的時候:
> db.newCollection.find().pretty() { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } {"_id" : ObjectId("5cc102fb33907ae66490e46d"),"name" : "wmyskxz","age" : 22 } {"_id" : ObjectId("5cc108fb33907ae66490e46e"),"name" : "wmyskxz-test","age" : 22,"x" : 1,"y" : 30 }3.2.3 更新(Update)
MongoDB 提供?update?方法更新文檔:
- db.collection.updateOne()?更新最多一個符合條件的文檔
- db.collection.updateMany()?更新所有符合條件的文檔
- db.collection.replaceOne()?替代最多一個符合條件的文檔
- db.collection.update()?默認更新一個文檔,可配置 multi 參數,跟新多個文檔
以?update()?方法為例。其格式:
> db.collection.update(<query>,<update>,{upsert: <boolean>,multi: <boolean>} )各參數意義:
- query 為查詢條件
- update 為修改的文檔
- upsert 為真,查詢為空時插入文檔
- multi 為真,更新所有符合條件的文檔
下面我們測試把?name?字段為?wmyskxz?的文檔更新一下試試:
> db.newCollection.update({name:"wmyskxz"},{name:"wmyskxz",age:30}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })要注意的是,如果更新文檔只傳入?age?字段,那么文檔會被更新為{age: 30},而不是{name:"wmyskxz", age:30}。要避免文檔被覆蓋,需要用到?$set?指令,$set?僅替換或添加指定字段:
> db.newCollection.update({name:"wmyskxz"},{$set:{age:30}})如果要在查詢的文檔不存在的時候插入文檔,要把 upsert 參數設置真值:
> db.newCollection.update({name:"wmyskxz11"},{$set:{age:30}},{upsert:true})update 方法默認情況只更新一個文檔,如果要更新符合條件的所有文檔,要把 multi 設為真值,并使用?$set?指令:
> db.newCollection.update({age:{$gt:20}},{$set:{test:"A"}},{multi:true}) WriteResult({ "nMatched" : 3, "nUpserted" : 0, "nModified" : 3 }) > db.newCollection.find() { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } { "_id" : ObjectId("5cc102fb33907ae66490e46d"), "name" : "wmyskxz", "age" : 30, "test" : "A" } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30, "test" : "A" } { "_id" : ObjectId("5cc110148d0a578f03d43e81"), "name" : "wmyskxz11", "age" : 30, "test" : "A" }3.2.4 刪除(Delete)
MongoDB 提供了?delete?方法刪除文檔:
- db.collection.deleteOne()?刪除最多一個符合條件的文檔
- db.collection.deleteMany()?刪除所有符合條件的文檔
- db.collection.remove()?刪除一個或多個文檔
以 remove 方法為例:
> db.newCollection.remove({name:"wmyskxz11"}) > db.newCollection.remove({age:{$gt:20}},{justOne:true}) > db.newCollection.find() { "_id" : ObjectId("5cc1026533907ae66490e46c"), "x" : 1 } { "_id" : ObjectId("5cc108fb33907ae66490e46e"), "name" : "wmyskxz-test", "age" : 22, "x" : 1, "y" : 30, "test" : "A" }MongoDB 提供了 drop 方法刪除集合,返回 true 表面刪除集合成功:
> db.newCollection.drop()3.2.5 小結
相比傳統關系型數據庫,MongoDB 的 CURD 操作更像是編寫程序,更符合開發人員的直覺,不過 MongoDB 同樣也支持 SQL 語言。MongoDB 的 CURD 引擎配合索引技術、數據聚合技術和 JavaScript 引擎,賦予 MongoDB 用戶更強大的操縱數據的能力。
參考文章:簡明 MongoDB 入門教程 - https://segmentfault.com/a/1190000010556670
4 MongoDB 數據模型的一些討論
前置申明:這一部分基于以下鏈接整理?https://github.com/justinyhuang/the-little-mongodb-book-cn/blob/master/mongodb.md#%E8%AE%B8%E5%8F%AF%E8%AF%81
這是一個抽象的話題,與大多數NoSQL方案相比,在建模方面,面向文檔的數據庫算是和關系數據庫相差最小的。這些差別是很小,但是并不是說不重要。
4.1 沒有連接(Join)
您要接受的第一個也是最基本的一個差別,就是?MongoDB 沒有連接(join)。我不知道MongoDB不支持某些類型連接句法的具體原因,但是我知道一般而言人們認為連接是不可擴展的。也就是說,一旦開始橫向分割數據,最終不可避免的就是在客戶端(應用程序服務器)使用連接。且不論MongoDB為什么不支持連接,事實是數據是有關系的,可是MongoDB不支持連接。(譯者:這里的關系指的是不同的數據之間是有關聯的,對于沒有關系的數據,就完全不需要連接。)
為了在沒有連接的MongoDB中生存下去,在沒有其他幫助的情況下,我們必須在自己的應用程序中實現連接。
基本上我們需要用第二次查詢去找到相關的數據。找到并組織這些數據相當于在關系數據庫中聲明一個外來的鍵?,F在先別管什么獨角獸了,我們來看看我們的員工。首先我們創建一個員工的數據(這次我告訴您具體的_id值,這樣我們的例子就是一樣的了):
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d730"), name: 'Leto'})然后我們再加入幾個員工并把?Leto?設成他們的老板:
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d731"), name: 'Duncan', manager: ObjectId("4d85c7039ab0fd70a117d730")}); db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d732"), name: 'Moneo', manager: ObjectId("4d85c7039ab0fd70a117d730")});*(有必要再強調一下,_id可以是任何的唯一的值。在實際工作中你很可能會用到ObjectId, 所以我們在這里也使用它)*
顯然,要找到Leto的所有員工,只要執行:
db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})沒什么了不起的。在最糟糕的情況下,為彌補連接的缺失需要做的只是再多查詢一次而已,該查詢很可能是經過索引了的。
4.1.1 數組和嵌入文檔(Embedded Documents)
MongoDB 沒有連接并不意味著它沒有其他的優勢。還記得我們曾說過 MongoDB 支持數組并把它當成文檔中的一級對象嗎?當處理多對一或是多對多關系的時候,這一特性就顯得非常好用了。用一個簡單的例子來說明,如果一個員工有兩個經理,我們可以把這個關系儲存在一個數組當中:
({name: 'Siona', manager: [ObjectId("4d85c7039ab0fd70a117d730"), ObjectId("4d85c7039ab0fd70a117d732")] })需要注意的是,在這種情況下,有些文檔中的?manager?可能是一個向量,而其他的卻是數組。在兩種情況下,前面的?find?還是一樣可以工作:
db.employees.find({manager: ObjectId("4d85c7039ab0fd70a117d730")})很快您就會發現數組中的值比起多對多的連接表(join-table)來說要更容易處理。
除了數組,MongoDB 還支持嵌入文檔。嘗試插入含有內嵌文檔的文檔,像這樣:
db.employees.insert({_id: ObjectId("4d85c7039ab0fd70a117d734"), name: 'Ghanima', family: {mother: 'Chani', father: 'Paul', brother: ObjectId("4d85c7039ab0fd70a117d730")}})也許您會這樣想,確實也可以這樣做:嵌入文檔可以用‘.’符號來查詢:
db.employees.find({'family.mother': 'Chani'})就這樣,我們簡要地介紹了嵌入文檔適用的場合以及您應該怎樣使用它。
4.1.2 DBRef
MongoDB 支持一個叫做 DBRef 的功能,許多 MongoDB 的驅動都提供對這一功能的支持。當驅動遇到一個?DBRef?時它會把當中引用的文檔讀取出來。DBRef?包含了所引用的文檔的 ID 和所在的集合。它通常專門用于這樣的場合:相同集合中的文檔需要引用另外一個集合中的不同文檔。例如,文檔 1 的?DBRef?可能指向?managers?中的文檔,而文檔 2 中的?DBRef?可能指向?employees?中的文檔。
4.1.3 范規范化(Denormalization)
代替連接的另一種方法就是反規范化數據。在過去,反規范化是為性能敏感代碼所設,或者是需要數據快照(例如審計日志)的時候才應用的。然而,隨著NoSQL的日漸普及,有許多這樣的數據庫并不提供連接操作,于是作為規范建模的一部分,反規范化就越來越常見了。這樣說并不是說您就需要為每個文檔中的每一條信息創建副本。與此相反,與其在設計的時候被復制數據的擔憂牽著走,還不如按照不同的信息應該歸屬于相應的文檔這一思路來對數據建模。
比如說,假設您在編寫一個論壇的應用程序。把一個?user?和一篇?post?關聯起來的傳統方法是在?posts?中加入一個?userid?的列。這樣的模型中,如果要顯示?posts?就不得不讀取(連接)users。一種簡單可行的替代方案就是直接把?name?和?userid?存儲在?post?中。您甚至可以用嵌入文檔來實現,比如說?user: {id: ObjectId('Something'), name: 'Leto'}。當然,如果允許用戶更改他們的用戶名,那么每當有用戶名修改的時候,您就需要去更新所有的文檔了(這需要一個額外的查詢)。
對一些人來說改用這種方法并非易事。甚至在一些情況下根本行不通。不過別不敢去嘗試這種方法:有時候它不僅可行,而且就是正確的方法。
4.1.4 應該選擇哪一種?
當處理一對多或是多對多問題的時候,采用id數組往往都是正確的策略??梢赃@么說,DBRef?并不是那么常用,雖然您完全可以試著采用這項技術。這使得新手們在面臨選擇嵌入文檔還是手工引用(manual reference)時猶豫不決。
首先,要知道目前一個單獨的文檔的大小限制是 4MB,雖然已經比較大了。了解了這個限制可以為如何使用文檔提供一些思路。目前看來多數的開發者還是大量地依賴手工引用來維護數據的關系。嵌入文檔經常被使用,but mostly for small pieces of data which we want to always pull with the parent document。一個真實的例子,我把?accounts?文檔嵌入存儲在用戶的文檔中,就像這樣:
db.users.insert({name: 'leto', email: 'leto@dune.gov', account: {allowed_gholas: 5, spice_ration: 10}})這不是說您就應該低估嵌入文檔的作用,也不是說應該把它當成是鮮少用到的工具并直接忽略。將數據模型直接映射到目標對象上可以使問題變得更加簡單,也往往因此而不再需要連接操作。當您知道 MongoDB 允許對嵌入文檔的域進行查詢并做索引后,這個說法就尤其顯得正確了。
4.2 集合:少一些還是多一些?
既然集合不強制使用模式,那么就完全有可能用一個單一的集合以及一個不匹配的文檔構建一個系統。以我所見過的情況,大部分的 MongoDB 系統都像您在關系數據庫中所見到的那樣布局。換句話說,如果在關系數據庫中會用表,那么很有可能在 MongoDB 中就要用集合(多對多連接表在這里是一個不可忽視的例外)
當把嵌入文檔引進來的時候,討論就會變得更加有意思了。最常見的例子就是博客系統。是應該分別維護?posts?和?comments?兩個集合,還是在每個?post?中嵌入一個?comments?數組?暫且不考慮那個 4MB 的限制(哈姆雷特所有的評論也不超過200KB,誰的博客會比他更受歡迎?),大多數的開發者還是傾向于把數據劃分開。因為這樣既簡潔又明確。
沒有什么硬性的規定(呃,除了 4MB 的限制)。做了不同的嘗試之后您就可以憑感覺知道怎樣做是對的了。
總結
至此已經對 MongoDB 有了一個基本的了解和入門,但是要運用在實際的項目中仍然有許多實踐需要自己去完成
按照慣例黏一個尾巴:
歡迎轉載,轉載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
總結
以上是生活随笔為你收集整理的MongoDB【快速入门】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 了解【Docker】从这里开始
- 下一篇: Elasticsearch【快速入门】