我們通過學習 MongoDB 的基本工作原理,開始我們的 MongoDB 之旅。當然,這是學習 MongoDB 的核心,它也能幫助我們回答諸如,MongoDB 適用于哪些場景這些更高層次的問題。
開始之前,這有六個簡單的概念我們需要了解一下。
1. MongoDB中的?`database`?有著和你熟知的"數據庫"一樣的概念 (對 Oracle 來說就是 schema)。一個 MongoDB 實例中,可以有零個或多個數據庫,每個都作為一個高等容器,用于存儲數據。
2. 數據庫中可以有零個或多個?`collections`?(集合)。集合和傳統意義上的?`table`?基本一致,你可以簡單的把兩者看成是一樣的東西。
3. 集合是由零個或多個?`documents`?(文檔)組成。同樣,一個文檔可以看成是一?`row`。
4. 文檔是由零個或多個?`fields`?(字段)組成。, 沒錯,它就是?`columns`。
5. `Indexes`?(索引)在 MongoDB 中扮演著和它們在 RDBMS 中一樣的角色。
6. `Cursors`?(游標)和上面的五個概念都不一樣,但是它非常重要,并且經常被忽視,因此我覺得它們值得單獨討論一下。其中最重要的你要理解的一點是,游標是,當你問 MongoDB 拿數據的時候,它會給你返回一個結果集的指針而不是真正的數據,這個指針我們叫它游標,我們可以拿游標做我們想做的任何事情,比如說計數或者跨行之類的,而無需把真正的數據拖下來,在真正的數據上操作。
綜上,MongoDB 是由包含?`collections`?的?`databases`?組成的。而?`collection`?是由?`documents`組成。每個?`document`是由?`fields`?組成。?`Collections`?可以被?`indexed`,以便提高查找和排序的性能。最后,當我們從 MongoDB 獲取數據的時候,我們通過?`cursor`?來操作,讀操作會被延遲到需要實際數據的時候才會執行。
那為什么我們需要新的術語(collection vs. table, document vs. row and field vs. column)?為了讓看起來更復雜點?事實上,雖然這些概念和關系型數據中的概念類似,但是還是有差異的。核心差異在于,關系型數據庫是在?`table`?上定義的`columns`,而面向文檔數據庫是在?`document`?上定義的?`fields`。也就是說,在?`collection`?中的每個?`document`?都可以有它自己獨立的?`fields`。因此,對于?`collection`?來說是個簡化了的?`table`?,但是一個?`document`?卻比一?`row`?有更多的信息。
雖然這些概念很重要,但是如果現在搞不明白也不要緊。多插幾條數據就明白上面說的到底是什么意思了。反正,要點就是,集合不對存儲內容嚴格限制 (所謂的無模式(schema-less))。字段由每個獨立的文檔進行跟蹤處理。這樣做的優點和缺點將在下面章節一一討論。
好了我們開始吧。如果你還沒有運行 MongoDB,那么快去運行?`mongod`?服務和開啟 mongo shell。shell 用的是 JavaScript。你可以試試一些全局命令,比如?`help`?或者?`exit`。如果要操作當前數據庫,用?`db`?,比如?`db.help()`?或者`db.stats()`。如果要操作指定集合,大多數情況下我們會操作集合而不是數據庫,用?`db.COLLECTION_NAME`?,比如`db.unicorns.help()`?或者?`db.unicorns.count()`。
我們繼續,輸入?`db.help()`,就能拿到一個對?`db`?能執行的所有的命令的列表。
順便說一句:因為這是一個 JavaScript shell,如果你輸入的命令漏了?`()`,你會看到這個命令的源碼,而不是執行這個命令。我提一下,是為了避免你執行漏了括號的命令,拿到一個以?`function (...){`?開頭的返回的時候,覺得神奇不可思議。比如說,如果你輸入?`db.help`?(不帶括號), 你會看到?`help`?方法的內部實現。
首先我們用全局的?`use`?來切換數據庫,繼續,輸入?`use learn`。這個數據庫實際存在與否完全沒有關系。我們在里面生成集合的時候,?`learn`?數據庫會自動建起來。現在,我們在一個數據庫里面了,你可以開始嘗試一下數據庫命令,比如`db.getCollectionNames()`。執行之后,你會得到一個空數組 (`[ ]`)。因為集合是無模式的,我們不需要特地去配置它。我們可以簡單的插入一個文檔到一個新的集合。像這樣,我們用?`insert`?命令,在文檔中插入:
~~~
db.unicorns.insert({name: 'Aurora',
gender: 'f', weight: 450})
~~~
這行命令對集合?`unicorns`?執行了?`insert`?命令,并傳入一個參數。MongoDB 內部用二進制序列化 JSON 格式,稱為 BSON。外部,也就是說我們多數情況應該用 JSON,就像上面的參數一樣。然后我們執行?`db.getCollectionNames()`?,我們將能拿到兩個集合:?`unicorns`?和?`system.indexes`。在每個數據庫中都會有一個?`system.indexes`?集合,用來保存我們數據的的索引信息。
你現在可以對用?`unicorns`?執行?`find`?命令,然后返回文檔列表:
~~~
db.unicorns.find()
~~~
請注意,除你指定的字段之外,會多出一個?`_id`?字段。每個文檔都會有一個唯一?`_id`?字段。你可以自己生成一個,或者讓 MongoDB 幫你生成一個?`ObjectId`?類型的。多數情況下,你會樂意讓 MongoDB 幫你生成的。默認的?`_id`?字段是已被索引的 - 這就說明了為什么會有?`system.indexes`?集合。你可以看看?`system.indexes`:
~~~
db.system.indexes.find()
~~~
你可以看到索引的名字,被索引的數據庫和集合,以及在索引中的字段。
現在,回到我們關于數組無模式的討論中來。往?`unicorns`?插入一個完全不同的文檔,比如:
~~~
db.unicorns.insert({name: 'Leto',
gender: 'm',
home: 'Arrakeen',
worm: false})
~~~
然后,再用?`find`?列出文檔。等我們理解再深入一點的時候,將會討論一下 MongoDB 的有趣行為。到這里,我希望你開始理解,為什么那些傳統的術語在這里不適用了。
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#掌握選擇器selector)掌握選擇器(Selector)
除了我們介紹過的六個概念,在開始討論更深入的話題之前,MongoDB 還有一個應該掌握的實用概念:查詢選擇器。MongoDB 的查詢選擇器就像 SQL 語句里面的?`where`?一樣。因此,你會在對集合的文檔做查找,計數,更新,刪除的時候用到它。選擇器是一個 JSON 對象,最簡單的是就是用?`{}`?匹配所有的文檔。如果我們想找出所有母獨角獸,我們可以用?`{gender:'f'}`。
開始深入學習選擇器之前,讓我們先做些準備。首先,把剛才我們插入?`unicorns`?集合的數據刪除,通過:`db.unicorns.remove({})`。現在,再插入一些用來演示的數據 (你不會手打吧):
~~~
db.unicorns.insert({name: 'Horny',
dob: new Date(1992,2,13,7,47),
loves: ['carrot','papaya'],
weight: 600,
gender: 'm',
vampires: 63});
db.unicorns.insert({name: 'Aurora',
dob: new Date(1991, 0, 24, 13, 0),
loves: ['carrot', 'grape'],
weight: 450,
gender: 'f',
vampires: 43});
db.unicorns.insert({name: 'Unicrom',
dob: new Date(1973, 1, 9, 22, 10),
loves: ['energon', 'redbull'],
weight: 984,
gender: 'm',
vampires: 182});
db.unicorns.insert({name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
weight: 575,
gender: 'm',
vampires: 99});
db.unicorns.insert({name: 'Solnara',
dob: new Date(1985, 6, 4, 2, 1),
loves:['apple', 'carrot',
'chocolate'],
weight:550,
gender:'f',
vampires:80});
db.unicorns.insert({name:'Ayna',
dob: new Date(1998, 2, 7, 8, 30),
loves: ['strawberry', 'lemon'],
weight: 733,
gender: 'f',
vampires: 40});
db.unicorns.insert({name:'Kenny',
dob: new Date(1997, 6, 1, 10, 42),
loves: ['grape', 'lemon'],
weight: 690,
gender: 'm',
vampires: 39});
db.unicorns.insert({name: 'Raleigh',
dob: new Date(2005, 4, 3, 0, 57),
loves: ['apple', 'sugar'],
weight: 421,
gender: 'm',
vampires: 2});
db.unicorns.insert({name: 'Leia',
dob: new Date(2001, 9, 8, 14, 53),
loves: ['apple', 'watermelon'],
weight: 601,
gender: 'f',
vampires: 33});
db.unicorns.insert({name: 'Pilot',
dob: new Date(1997, 2, 1, 5, 3),
loves: ['apple', 'watermelon'],
weight: 650,
gender: 'm',
vampires: 54});
db.unicorns.insert({name: 'Nimue',
dob: new Date(1999, 11, 20, 16, 15),
loves: ['grape', 'carrot'],
weight: 540,
gender: 'f'});
db.unicorns.insert({name: 'Dunx',
dob: new Date(1976, 6, 18, 18, 18),
loves: ['grape', 'watermelon'],
weight: 704,
gender: 'm',
vampires: 165});
~~~
現在我們有數據了,我們可以開始來學習掌握選擇器了。`{field: value}`?用來查找那些?`field`?的值等于?`value`?的文檔。?`{field1: value1, field2: value2}`?相當于?`and`?查詢。還有?`$lt`,?`$lte`,?`$gt`,?`$gte`?和?`$ne`?被用來處理 小于,小于等于,大于,大于等于,和不等于操作。比如,獲取所有體重大于700磅的公獨角獸,我們可以這樣:
~~~
db.unicorns.find({gender: 'm',
weight: {$gt: 700}})
//or (not quite the same thing, but for
//demonstration purposes)
db.unicorns.find({gender: {$ne: 'f'},
weight: {$gte: 701}})
~~~
`$exists`?用來匹配字段是否存在,比如:
~~~
db.unicorns.find({
vampires: {$exists: false}})
~~~
會返回一條文檔。'$in' 被用來匹配查詢文檔在我們傳入的數組參數中是否存在匹配值,比如:
~~~
db.unicorns.find({
loves: {$in:['apple','orange']}})
~~~
會返回那些喜歡?`apple`?或者?`orange`?的獨角獸。
如果我們想要 OR 而不是 AND 來處理選擇條件的話,我們可以用?`$or`?操作符,再給它一個我們要匹配的數組:
~~~
db.unicorns.find({gender: 'f',
$or: [{loves: 'apple'},
{weight: {$lt: 500}}]})
~~~
上面的查詢會返回那些喜歡?`apples`?或者?`weigh`?小于500磅的母獨角獸。
在我們最后兩個例子里面有個非常贊的特性。你應該已經注意到了,`loves`?字段是個數組。MongoDB 允許數組作為基本對象(first class objects)處理。這是個令人難以置信的超贊特性。一旦你開始用它,你都不知道沒了它你怎么活下去了。最有趣的是,基于數組的查詢變得非常簡單:?`{loves: 'watermelon'}`?會把文檔中?`loves`?中有?`watermelon`?的值全部查詢出來。
除了我們介紹的這些,還有更多可用的操作。所有這些都記載在 MongoDB 手冊上的?[Query Selectors](http://docs.mongodb.org/manual/reference/operator/query/#query-selectors)?這一章。我們介紹的僅僅是那些你學習時所需要用到的,同時也是你最經常用到的操作。
我們已經學習了選擇器是如何配合?`find`?命令使用的了。還大致介紹了一下如何配合?`remove`?命令使用,`count`?命令雖然沒介紹,不過你肯定知道應該怎么做,而?`update`?命令,之后我們會花多點時間來詳細學習它。
MongoDB 為我們的?`_id`?字段生成的?`ObjectId`?可以這樣查詢:
~~~
db.unicorns.find(
{_id: ObjectId("TheObjectId")})
~~~
## [](https://github.com/geminiyellow/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown#小結)小結
我們還沒有看到?`update`?, 或是能拿來做更華麗事情的?`find`。不過,我們已經安裝好 MongoDB 并運行起來了, 簡略的介紹了一下?`insert`?和?`remove`?命令 (完整版也沒比我們介紹的多什么)。 我們還介紹了?`find`?以及了解了 MongoDB`selectors`?是怎么一回事。 我們起了個很好的頭,并為以后的學習奠定了堅實基礎。 信不信由你,其實你已經掌握了學習 MongoDB 所必須的大多數知識 - 它真的是易學易用。 我強烈建議你在繼續學習之前在本機上多試試多玩玩。 插入不同的文檔,可以試試看在不同的集合中,習慣一下使用不同的選擇器。試試?`find`,?`count`?和?`remove`。 多試幾次之后,你會發現原來看起來那么格格不入的東西,用起來居然水到渠成。