MongoDB【快速入門】

1.MongDB 簡介

MongoDB(來自於英文單詞「Humongous」,中文含義爲「龐大」)是能夠應用於各類規模的企業、各個行業以及各種應用程序的開源數據庫。做爲一個適用於敏捷開發的數據庫,MongoDB 的數據模式能夠隨着應用程序的發展而靈活地更新。與此同時,它也爲開發人員 提供了傳統數據庫的功能:二級索引,完整的查詢系統以及嚴格一致性等等。 MongoDB 可以使企業更加具備敏捷性和可擴展性,各類規模的企業均可以經過使用 MongoDB 來建立新的應用,提升與客戶之間的工做效率,加快產品上市時間,以及下降企業成本。html

MongoDB 是專爲可擴展性,高性能和高可用性而設計的數據庫。它能夠從單服務器部署擴展到大型、複雜的多數據中心架構。利用內存計算的優點,MongoDB 可以提供高性能的數據讀寫操做。 MongoDB 的本地複製和自動故障轉移功能使您的應用程序具備企業級的可靠性和操做靈活性。git

以上內容摘自官網:github

1.1 文檔型數據庫

簡而言之,MongoDB是一個免費開源跨平臺的 NoSQL 數據庫,與關係型數據庫不一樣,MongoDB 的數據以相似於 JSON 格式的二進制文檔存儲:mongodb

{
    name: "我沒有三顆心臟",
    age: 22,
}

文檔型的數據存儲方式有幾個重要好處:shell

  1. 文檔的數據類型能夠對應到語言的數據類型,如數組類型(Array)和對象類型(Object);
  2. 文檔能夠嵌套,有時關係型數據庫涉及幾個表的操做,在 MongoDB 中一次就能完成,能夠減小昂貴的鏈接花銷;
  3. 文檔不對數據結構加以限制,不一樣的數據結構能夠存儲在同一張表;
  4. MongoDB 的文檔數據模型和索引系統能有效提高數據庫性能;
  5. 複製集功能提供數據冗餘,自動化容災容錯,提高數據庫可用性;
  6. 分片技術可以分散單服務器的讀寫壓力,提升併發能力,提高數據庫的可拓展性;
  7. MongoDB 高性能,高可用性、可擴展性等特色,使其至 2009 年發佈以來,逐漸被承認,並被愈來愈多的用於生產環境中。AWS、GCP、阿里雲等雲平臺都提供了十分便捷的 MongoDB 雲服務。

1.2 MongoDB 基礎概念

可使用咱們熟悉的 MySQL 數據庫來加以對比:數據庫

MySQL 基礎概念 MongoDB 對應概念
數據庫(database) 容器(database)
表(table) 集合(collection)
行(row) 文檔(document)
列(column) 域(filed)
索引(index) 索引(index)

也借用一下菜鳥教程的圖來更加形象生動的說明一下:json

這很容易理解,可是問題在於:咱們爲何要引入新的概念呢?(也就是爲何咱們要把「表」替換成「集合」,「行」替換成「文檔」,「列」替換成「域」呢?)緣由在於,其實在 MySQL 這樣的典型關係型數據中,咱們是在定義表的時候定義列的,可是因爲上述文檔型數據庫的特色,它容許文檔的數據類型能夠對應到語言的數據類型,因此咱們是在定義文檔的時候纔會定義域的。segmentfault

也就是說,集合中的每一個文檔均可以有獨立的域。所以,雖然說集合相對於表來講是一個簡化了的容器,而文檔則包含了比行要多得多的信息。數組

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 shellshell 會報出一連串權限警告,不過不用擔憂,這並不會影響以後的操做。在添加受權用戶和開啓認證後,這些警告會自動消失。

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:22age:"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!

除此以外,還能夠經過 countskiplimit 等指針(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中生存下去,在沒有其餘幫助的狀況下,咱們必須在本身的應用程序中實現鏈接。

基本上咱們須要用第二次查詢去找到相關的數據。找到並組織這些數據至關於在關係數據庫中聲明一個外來的鍵。如今先別管什麼獨角獸了,咱們來看看咱們的員工。首先咱們建立一個員工的數據(此次我告訴您具體的_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。一種簡單可行的替代方案就是直接把 nameuserid 存儲在 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 中就要用集合(多對多鏈接表在這裏是一個不可忽視的例外)

當把嵌入文檔引進來的時候,討論就會變得更加有意思了。最多見的例子就是博客系統。是應該分別維護 postscomments 兩個集合,仍是在每一個 post 中嵌入一個 comments 數組?暫且不考慮那個 4MB 的限制(哈姆雷特全部的評論也不超過200KB,誰的博客會比他更受歡迎?),大多數的開發者仍是傾向於把數據劃分開。由於這樣既簡潔又明確。

沒有什麼硬性的規定(呃,除了 4MB 的限制)。作了不一樣的嘗試以後您就能夠憑感受知道怎樣作是對的了。

總結

至此已經對 MongoDB 有了一個基本的瞭解和入門,可是要運用在實際的項目中仍然有許多實踐須要本身去完成


按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz 分享本身的學習 & 學習資料 & 生活 想要交流的朋友也能夠加qq羣:3382693

相關文章
相關標籤/搜索