傳統的計算機應用大多使用關係型數據庫來存儲數據,好比你們可能熟悉的MySql, Sqlite等等,它的特色是數據以表格(table)的形式儲存起來的。數據庫由一張張排列整齊的表格構成,就好像一個Excel表單同樣,每一個表格會有若干列,好比一個學生信息表,可能包含學號、姓名、性別、入學年份、高考成績、籍貫等等。而表格的每一排,則是一個個學生的具體信息。在企業級應用和前互聯網時代,關係型數據庫幾乎是不二選擇。關係型數據庫的特色是有整齊劃一的組織,很方便對數據進行描述、插入、搜索。git
想象有一個傳統的網上服裝商店吧,它的主要的數據多是儲存在一張叫products的表單裏,表單可能包含這些列:商品編號(ID)、名稱(Name)、商家(brand)、主目錄(cate)、子目錄(sub-cat)、零售價(price)、是否促銷(promotion)等等。若是有一個用戶想要查找全部價格低於300元的正在促銷的鞋子的編號和名稱,則能夠執行相似於如下的SQL語句:github
SELECT ID, name FROM products WHERE cate='shoes' AND price<300 and AND promotion=true;
SQL具有了強大了的深度查詢能力,能知足各式各樣的查詢要求。而若是要對數據進行添加和刪除,成本也是很是低的。這些是SQL的優點之一, 但隨着互聯網的興起以及數據形式的多樣化,四平八穩的SQL表單在一些領域漸漸顯現出它的劣勢。讓咱們經過一個例子來講明。考慮一個博客後臺系統,若是咱們用關係型數據庫爲每篇博客(article)建一個表單的話,這個表單大概會包括如下這些列:web
ID | Title | Description | Author | Content | Likes |
---|---|---|---|---|---|
A_1 | Title1 | Political Article | Joe | Content 1 | 12 |
A_2 | Title2 | Humorous Story | Sam | Content 2 | 50 |
這時候用SQL數據庫來存儲是很是方便的,但假如咱們要位每篇文章添加評論功能,會發現每篇文章可能要多篇評論,並且這個數目是動態變化的,並且每篇評論還包括好幾項內容:評論的人、評論的時間、以及評論內容。這時候要將這些內容都塞進上述的那個表,就顯得很困難。一般的作法是爲評論(comment)單獨建一個表:正則表達式
ID | Author | Time | Content | Article |
---|---|---|---|---|
C_1 | Anna | 2014-12-26 08:23 | Really good articles! | A_1 |
C_2 | David | 2014-12-25 09:30 | I like it! | A_1 |
相似地,每篇文章可能會有若干標籤(tags)。標籤自己又是一個表單:mongodb
ID | Category | Tags | Content | Article |
---|---|---|---|---|
T_1 | Anna | 2014-12-26 08:23 | Really good articles! | A_1 |
T_2 | David | 2014-12-25 09:30 | I like it! | A_2 |
而博客的表格則要經過foreign key跟這些相關聯的表格聯繫起來(可能還包括做者、出版社等其它表格)。這樣一來,當咱們作查詢的時候,好比說,「找出評論數很多於3的標籤爲‘政治評論’的做者爲Sam的文章」,就會涉及到複雜的跨表查詢,須要大量使用join
語句。這種跨表查詢不只下降了查詢速度,並且這些語句寫起來也不簡單。數據庫
那麼,若是用MongoDB數據庫來實現,能夠如何設計數據模型呢?很簡單,像下面這樣:express
_id: POST_ID title: TITLE_OF_POST, description: POST_DESCRIPTION, author: POST_BY, tags: [TAG1, TAG2, TAG3], likes: TOTAL_LIKES, comments: [ { user:'COMMENT_BY', message: TEXT, dateCreated: DATE_TIME, }, { user:'COMMENT_BY', message: TEXT, dateCreated: DATE_TIME, } ]
在MongoDB裏,每篇博客文章以一個文檔(document)的形式保存起來,而文檔內部包含了不少項目,好比title tags
等,每個項目都是key-value
的形式,即有一個項目的名字,好比title
,以及它的值TITLE_OF_POST
。而重要的是,一個key
能夠有多個values
,他們用[]
括起來。學習
這種「寬鬆」的數據存儲形式很是靈活,MongoDB不限制每一個key
對應的values
的數目。好比有的文章沒有評論,則它的值就是一個空集,徹底沒有問題;有的文章評論不少,也能夠無限制地插入。更靈活的是,MongoDB不要求同一個集合(collection,至關於SQL的table)裏面的不一樣document有相同的key,好比除了上述這種文檔組織,有的文檔所表明的文章可能沒有likes這個項目,再好比有的文章可能有更多的項目,好比可能還有dislikes等等。這些不一樣的文檔均可以靈活地存儲在同一個集合下,並且查詢起來也異常簡單,由於都在一個文檔裏,不用進行各類跨文檔查詢。而這種MongoDB式的存儲也方便了數據的維護,對於一篇博客文章來講,全部的相關數據都在這個document裏面,不用去考慮一個數據操做須要involve多少個表格。測試
固然,除了上述的優勢,MongoDB還有很多別的優點,好比MongoDB的數據是用JSON(Javascript Object Notation)存儲的(就是上面的這種key-value的形式),而幾乎全部的web應用都是基於Javascript的。所以,存儲的數據和應用的數據的格式是高度一致的,不需通過轉換。更多的優勢能夠查看:[2]。網站
這個極簡教程,或者說筆記,並非一個覆蓋MongoDB方方面面的教程。所謂極簡的意思,就是隻選取那些最重要、最經常使用的內容進行基於實例的介紹,從而讓讀者可以在最短的時間內快速上手,而且能順利地進行後續的縱深的學習。
具體地說,這個教程的特色是:
閱讀這篇文章不須要有特別的基礎,但最好知道數據庫的基本概念,若是自己熟悉SQL那就更好啦。
MongoDB能夠在Windows、Linux、Mac OS X等主流平臺運行,並且下載和安裝很是簡單,很是友好。這篇文檔的例子採用MongoDB 2.6版本,均在OS X測試過,有充足的理由相信,在其它平臺也能順利運行。
在上一節執行完步驟6後,你會看到命令行裏顯示:`connecting to: test`,這裏的`test`是默認的數據庫。這裏咱們能夠新建一個數據庫。在命令行裏打入:
use tutorial
這樣就新建了一個叫作tutorial
的數據庫。你能夠執行
show databases
來顯示當前的數據庫。不過這時候因爲咱們的新數據庫是空的,因此會顯示相似這樣的:
admin (empty) local 0.078GB
咱們試着往咱們的數據庫裏添加一個集合(collection),MongoDB裏的集合和SQL裏面的表格是相似的:
db.createCollection('author')
順利的話會顯示:
{ "ok" : 1 }
表示建立成功。
你能夠再回頭執行:
show databases
這時候咱們的tutorial集合已經位列其中。你能夠再執行
show collections
能夠看到建立的集合author也在其中。
咱們暫時不須要author這個集合,因此咱們能夠經過執行:
db.author.drop()
來將其刪除。這時候你再執行show collections
,就再也看不到咱們的author了。
這一節要記住的點主要只有一個:集合(collection)相似於SQL的表格(table),相似於Excel的一個個表格。
想象一個精簡版的「豆瓣電影」。咱們須要建立一個數據庫,來存儲每部電影的信息,電影的信息包括:
顯然咱們須要先建立一個叫電影的集合:
db.createCollection('movie')
而後,咱們就能夠插入數據了:
db.movie.insert( { title: 'Forrest Gump', directed_by: 'Robert Zemeckis', stars: ['Tom Hanks', 'Robin Wright', 'Gary Sinise'], tags: ['drama', 'romance'], debut: new Date(1994,7,6,0,0), likes: 864367, dislikes: 30127, comments: [ { user:'user1', message: 'My first comment', dateCreated: new Date(2013,11,10,2,35), like: 0 }, { user:'user2', message: 'My first comment too!', dateCreated: new Date(2013,11,11,6,20), like: 0 } ] } )
請注意,這裏插入數據以前,咱們並不須要先聲明movie這個集合裏面有哪些項目。咱們直接插入就能夠了~這一點和SQL不同,SQL必須先聲明一個table裏面有哪些列,而MongoDB不須要。
把上面的例子複製進命令行應該能夠順利運行,但我強烈建議你手動打一下,或者輸入一部你本身喜歡的電影。insert
操做有幾點須要注意:
若是你在insert
以後看到WriteResult({ "nInserted" : 1 })
,說明寫入成功。
這個時候你能夠用查詢的方式來返回數據庫中的數據:
db.movie.find().pretty()
這裏find()
裏面是空的,說明咱們不作限制和篩選,相似於SQL沒有WHERE
語句同樣。而pretty()
輸出的是經格式美化後的數據,你能夠本身試試沒有pretty()
會怎麼樣。
仔細觀察find()
的結果,你會發現多了一個叫'_id'
的東西,這是數據庫自動建立的一個ID號,在同一個數據庫裏,每一個文檔的ID號都是不一樣的。
咱們也能夠同時輸入多個數據:
db.movie.insert([ { title: 'Fight Club', directed_by: 'David Fincher', stars: ['Brad Pitt', 'Edward Norton', 'Helena Bonham Carter'], tags: 'drama', debut: new Date(1999,10,15,0,0), likes: 224360, dislikes: 40127, comments: [ { user:'user3', message: 'My first comment', dateCreated: new Date(2008,09,13,2,35), like: 0 }, { user:'user2', message: 'My first comment too!', dateCreated: new Date(2003,10,11,6,20), like: 14 }, { user:'user7', message: 'Good Movie!', dateCreated: new Date(2009,10,11,6,20), like: 2 } ] }, { title: 'Seven', directed_by: 'David Fincher', stars: ['Morgan Freeman', 'Brad Pitt', 'Kevin Spacey'], tags: ['drama','mystery','thiller'], debut: new Date(1995,9,22,0,0), likes: 134370, dislikes: 1037, comments: [ { user:'user3', message: 'Love Kevin Spacey', dateCreated: new Date(2002,09,13,2,35), like: 0 }, { user:'user2', message: 'Good works!', dateCreated: new Date(2013,10,21,6,20), like: 14 }, { user:'user7', message: 'Good Movie!', dateCreated: new Date(2009,10,11,6,20), like: 2 } ] } ])
順利的話會顯示:
BulkWriteResult({ "writeErrors" : [ ], "writeConcernErrors" : [ ], "nInserted" : 2, "nUpserted" : 0, "nMatched" : 0, "nModified" : 0, "nRemoved" : 0, "upserted" : [ ]
表面咱們成功地插入了兩個數據。注意批量插入的格式是這樣的:db.movie.insert([{ITEM1},{ITEM2}])
。幾部電影的外面須要用[]括起來。
請注意,雖然collection的插入不須要先聲明,但表達相贊成思的key,名字要同樣,好比,若是咱們在一個文檔裏用directed_by
來表示導演,則在其它文檔也要保持一樣的名字(而不是director
之類的)。不一樣的名字不是不能夠,技術上徹底可行,但會給查詢和更新帶來困難。
好了,到這裏,咱們就有了一個叫tutorial的數據庫,裏面有一個叫movie的集合,而movie裏面有三個記錄。接下來咱們就能夠對其進行查詢了。
在上一節咱們已經接觸到最簡單的查詢db.movie.find().pretty()
。MongoDB支持各類各樣的深度查詢功能。先來一個最簡單的例子,找出大衛芬奇(David Fincher)導演的全部電影:
db.movie.find({'directed_by':'David Fincher'}).pretty()
將返回《搏擊俱樂部》和《七宗罪》兩部電影。這種搜索和SQL的WHERE
語句是很類似的。
也能夠設置多個條件。好比找出大衛芬奇導演的, 摩根弗里曼主演的電影:
db.movie.find({'directed_by':'David Fincher', 'stars':'Morgan Freeman'}).pretty()
這裏兩個條件之間,是AND的關係,只有同時知足兩個條件的電影纔會被輸出。同理,能夠設置多個的條件,不贅述。
條件之間也能夠是或的關係,好比找出羅賓懷特或摩根弗里曼主演的電影:
db.movie.find( { $or: [ {'stars':'Robin Wright'}, {'stars':'Morgan Freeman'} ] }).pretty()
注意這裏面稍顯複雜的各類括號。
還能夠設置一個範圍的搜索,好比找出50萬人以上讚的電影:
db.movie.find({'likes':{$gt:500000}}).pretty()
一樣要注意略複雜的括號。注意,在這些查詢裏,key的單引號都是可選的,也就是說,上述語句也能夠寫成:
db.movie.find({likes:{$gt:500000}}).pretty()
相似地,少於二十萬人讚的電影:
db.movie.find({likes:{$lt:200000}}).pretty()
相似的運算符還有:$let
:小於或等於;$get
:大於或等於;$ne
:不等於。
注意,對於包含多個值的key,一樣能夠用find來查詢。好比:
db.movie.find({'tags':'romance'})
將返回《阿甘正傳》,雖然其標籤既有romance,又有drama,但只要符合一個就能夠了。
若是你確切地知道返回的結果只有一個,也能夠用findOne
:
db.movie.findOne({'title':'Forrest Gump'})
若是有多個結果,則會按磁盤存儲順序返回第一個。請注意,findOne()
自帶pretty模式,因此不能再加pretty()
,將報錯。
若是結果不少而你只想顯示其中一部分,能夠用limit()
和skip()
,前者指明輸出的個數,後者指明從第二個結果開始數。好比:
db.movie.find().limit(2).skip(1).pretty()
則跳過第一部,從第二部開始選取兩部電影。
第五節的時候咱們講了find
的用法,但對於符合條件的條目,咱們都是返回整個JSON文件的。這相似於SQL裏面的SELECT *
。有的時候,咱們須要的,僅僅是部分數據,這個時候,find
的局部查詢的功能就派上用場了。先來看一個例子,返回tags爲drama的電影的名字和首映日期。
db.movie.find({'tags':'drama'},{'debut':1,'title':1}).pretty()
數據庫將返回:
{ "_id" : ObjectId("549cfb42f685c085f1dd47d4"), "title" : "Forrest Gump", "debut" : ISODate("1994-08-05T16:00:00Z") } { "_id" : ObjectId("549cff96f685c085f1dd47d6"), "title" : "Fight Club", "debut" : ISODate("1999-11-14T16:00:00Z") } { "_id" : ObjectId("549cff96f685c085f1dd47d7"), "title" : "Seven", "debut" : ISODate("1995-10-21T16:00:00Z") }
這裏find的第二個參數是用來控制輸出的,1表示要返回,而0則表示不返回。默認值是0,但_id
是例外,所以若是你不想輸出_id
,須要顯式地聲明:
db.movie.find({'tags':'drama'},{'debut':1,'title':1,'_id':0}).pretty()
不少狀況下你須要更新你的數據庫,好比有人對某部電影點了個贊,那麼你須要更新相應的數據庫。好比有人對《七宗罪》點了個贊,而它原本的讚的個數是134370,那麼你須要更新到134371。能夠這樣操做:
db.movie.update({title:'Seven'}, {$set:{likes:134371}})
第一個大括號裏代表要選取的對象,第二個代表要改動的數據。請注意上述的操做至關不現實,由於你首先要知道以前的數字是多少,而後加一,但一般你不讀取數據庫的話,是不會知道這個數(134370)的。MongoDB提供了一種簡便的方法,能夠對現有條目進行增量操做。假設又有人對《七宗罪》點了兩個贊,則能夠:
db.movie.update({title:'Seven'}, {$inc:{likes:2}})
若是你查詢的話,會發現點贊數變爲134373了,這裏用的是$inc
。除了增量更新,MongoDB還提供了不少靈活的更新選項,具體能夠看:http://docs.mongodb.org/manual/reference/operator/update-field/ 。
注意若是有多部符合要求的電影。則默認只會更新第一個。若是要多個同時更新,要設置{multi:true}
,像下面這樣:
db.movie.update({}, {$inc:{likes:10}},{multi:true})
全部電影的贊數都多了10.
注意,以上的更新操做會替換掉原來的值,因此若是你是想在原有的值得基礎上增長一個值的話,則應該用$push
,好比,爲《七宗罪》添加一個popular的tags。
db.movie.update({'title':'Seven'}, {$push:{'tags':'popular'}})
你會發現《七宗罪》如今有四個標籤:
"tags" : [ "drama", "mystery", "thiller", "popular" ],
刪除的句法和find很類似,好比,要刪除標籤爲romance的電影,則:
db.movie.remove({'tags':'romance'})
考慮到咱們數據庫條目異常稀少,就不建議你執行這條命令了~
注意,上面的例子會刪除全部標籤包含romance的電影。若是你只想刪除第一個,則
db.movie.remove({'tags':'romance'},1)
若是不加任何限制:
db.movie.remove()
會刪除movie這個集合下的全部文檔。
爲文檔中的一些key加上索引(index)能夠加快搜索速度。這一點不難理解,假如沒有沒有索引,咱們要查找名字爲Seven的電影,就必須在全部文檔裏逐個搜索。而若是對名字這個key加上索引值,則電影名這個字符串和數字創建了映射,這樣在搜索的時候就會快不少。排序的時候也是如此,不贅述。MongoDB裏面爲某個key加上索引的方式很簡單,好比咱們要對導演這個key加索引,則能夠:
db.movie.ensureIndex({directed_by:1})
這裏的1是升序索引,若是要降序索引,用-1。
MongoDB支持對輸出進行排序,好比按名字排序:
db.movie.find().sort({'title':1}).pretty()
一樣地,1是升序,-1是降序。默認是1。
db.movie.getIndexes()
將返回全部索引,包括其名字。
而
db.movie.dropIndex('index_name')
將刪除對應的索引。
MongoDB支持相似於SQL裏面的GROUP BY
操做。好比當有一張學生成績的明細表時,咱們能夠找出每一個分數段的學生各有多少。爲了實現這個操做,咱們須要稍加改動咱們的數據庫。執行如下三條命令:
db.movie.update({title:'Seven'},{$set:{grade:1}}) db.movie.update({title:'Forrest Gump'},{$set:{grade:1}}) db.movie.update({title:'Fight Club'},{$set:{grade:2}})
這幾條是給每部電影加一個虛擬的分級,前兩部是歸類是一級,後一部是二級。
這裏你也能夠看到MongoDB的強大之處:能夠動態地後續添加各類新項目。
咱們先經過聚合來找出總共有幾種級別。
db.movie.aggregate([{$group:{_id:'$grade'}}])
輸出:
{ "_id" : 2 } { "_id" : 1 }
注意這裏的2和1是指級別,而不是每一個級別的電影數。這個例子看得清楚些:
db.movie.aggregate([{$group:{_id:'$directed_by'}}])
這裏按照導演名字進行聚合。輸出:
{ "_id" : "David Fincher" } { "_id" : "Robert Zemeckis" }
接着咱們要找出,每一個導演的電影數分別有多少:
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$sum:1}}}])
將會輸出:
{ "_id" : "David Fincher", "num_movie" : 2 } { "_id" : "Robert Zemeckis", "num_movie" : 1 }
注意$sum後面的1表示只是把電影數加起來,但咱們也能夠統計別的數據,好比兩位導演誰的贊比較多:
db.movie.aggregate([{$group:{_id:'$directed_by',num_likes:{$sum:'$likes'}}}])
輸出:
{ "_id" : "David Fincher", "num_likes" : 358753 } { "_id" : "Robert Zemeckis", "num_likes" : 864377 }
注意這些數據都純屬虛構啊!
除了$sum
,還有其它一些操做。好比:
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$avg:'$likes'}}}])
統計平均的贊。
db.movie.aggregate([{$group:{_id:'$directed_by',num_movie:{$first:'$likes'}}}]
返回每一個導演的電影中的第一部的贊數。
其它各類操做能夠參考:http://docs.mongodb.org/manual/reference/operator/aggregation/group/ 。
MongoDB支持單個文檔內的原子化操做(atomic operation),這是說,能夠將多條關於同一個文檔的指令放到一塊兒,他們要麼一塊兒執行,要麼都不執行。而不會執行到一半。有些場合須要確保多條執行一塊兒順次執行。好比一個場景:一個電商網站,用戶查詢某種商品的剩餘數量,以及用戶購買該種商品,這兩個操做,必須放在一塊兒執行。否則的話,假定咱們先執行剩餘數量的查詢,這是假定爲1,用戶接着購買,但假如這兩個操做之間還加入了其它操做,好比另外一個用戶搶先購買了,那麼原先購買用戶的購買的行爲就會形成數據庫的錯誤,由於實際上這種商品以及沒有存貨了。但由於查詢剩餘數量和購買不是在一個「原子化操做」以內,所以會發生這樣的錯誤[2]。
MongoDB提供了findAndModify
的方法來確保atomic operation。好比這樣的:
db.movie.findAndModify( { query:{'title':'Forrest Gump'}, update:{$inc:{likes:10}} } )
query是查找出匹配的文檔,和find是同樣的,而update則是更新likes這個項目。注意因爲MongoDB只支持單個文檔的atomic operation,所以若是query出多於一個文檔,則只會對第一個文檔進行操做。
findAndModify
還支持更多的操做,具體見:http://docs.mongodb.org/manual/reference/command/findAndModify/。
除了前面介紹的各類深度查詢功能,MongoDB還支持文本搜索。對文本搜索以前,咱們須要先對要搜索的key創建一個text索引。假定咱們要對標題進行文本搜索,咱們能夠先這樣:
db.movie.ensureIndex({title:'text'})
接着咱們就能夠對標題進行文本搜索了,好比,查找帶有"Gump"的標題:
db.movie.find({$text:{$search:"Gump"}}).pretty()
注意text和search前面的$符號。
這個例子裏,文本搜索做用不是很是明顯。但假設咱們要搜索的key是一個長長的文檔,這種text search的方便性就顯現出來了。MongoDB目前支持15種語言的文本搜索。
MongoDB還支持基於正則表達式的查詢。若是不知道正則表達式是什麼,能夠參考Wikipedia。這裏簡單舉幾個例子。好比,查找標題以b
結尾的電影信息:
db.movie.find({title:{$regex:'.*b$'}}).pretty()
也能夠寫成:
db.movie.find({title:/.*b$/}).pretty()
查找含有'Fight'標題的電影:
db.movie.find({title:/Fight/}).pretty()
注意以上匹配都是區分大小寫的,若是你要讓其不區分大小寫,則能夠:
db.movie.find({title:{$regex:'fight.*b',$options:'$i'}}).pretty()
$i
是insensitive的意思。這樣的話,即便是小寫的fight,也能搜到了。
至此,MongoDB的最基本的內容就介紹得差很少了。若是有什麼遺漏的之後我會補上來。若是你一路看到底徹底了這個入門教程,恭喜你,你必定是一個有毅力的人。
把這個文檔過一遍,不會讓你變成一個MongoDB的專家(若是會那就太奇怪了)。但若是它能或多或少減小你上手的時間,或者讓你意識到「咦,MongoDB其實沒那麼複雜」,那麼這個教程的目的也就達到啦。
這個文檔是匆忙寫就的,出錯簡直是必定的。若是您發現了任何錯誤或者有關於本文的任何建議,麻煩發郵件給我(stevenslxie at gmail.com)或者在GitHub上直接交流,不勝感激。