MongoDB 學習筆記2 - 基礎知識和使用

0. 背景

MongoDB 的一些基礎知識和使用。

MongoDB

1. 基礎知識

六個簡單的概念:

  • (1) database(數(shù)據(jù)庫):MongoDB中 也有 數(shù)據(jù)庫 的概念,和關系型數(shù)據(jù)中的的"數(shù)據(jù)庫"一樣的概念。一個 MongoDB 實例中,可以有零個或多個數(shù)據(jù)庫,用于存儲數(shù)據(jù)。
  • (2) collections (集合):一個數(shù)據(jù)庫中可以有多個 collections (集合)。它和傳統(tǒng)意義上的 table 是一樣的東西。
  • (3) documents (文檔):一個集合由多個 documents (文檔)組成。一個文檔可以看成是一條數(shù)據(jù)的記錄( row 或者 record)。
  • (4) fields (字段:一個文檔是由多個 fields (字段)組成。它就是 columns。
  • (5) Indexes (索引): MongoDB 的索引和 RDBMS 中的一樣。
  • (6) Cursors (游標):當你問 MongoDB 拿數(shù)據(jù)的時候,它會給你返回一個結果集的指針而不是真正的數(shù)據(jù),這個指針我們叫它游標。

概念對照表:

MongoDB 傳統(tǒng)的關系型數(shù)據(jù)庫
database database, 相同
collections table
documents row
fields columns
Indexes Indexes 相同
Cursors -

當我們從 MongoDB 獲取數(shù)據(jù)的時候,我們通過 cursor 來操作,讀操作會被延遲到需要實際數(shù)據(jù)的時候才會執(zhí)行。

核心差異在于,在MongoDB里,collection中的每個documents都可以有自己獨立的 field (字段),而關系型數(shù)據(jù)中每行的字段都智能相同

要點就是,集合不對存儲內(nèi)容嚴格限制 (所謂的無模式(schema-less))。字段由每個獨立的文檔進行跟蹤處理。

總結:MongoDB 可以每行數(shù)據(jù)的結構都不同,支持非結構化數(shù)據(jù)。 區(qū)別于 傳統(tǒng)的嚴格結構化數(shù)據(jù)。

2. 基本操作

2.1 連接到數(shù)據(jù)庫

MongoDB 的 shell
MongoDB 的 shell 是一個連接數(shù)據(jù)庫服務的客戶端控制臺工具。MongoDB 啟動shell ,在命令行輸入:

mongo

shell 用的是 JavaScript。
因為這是一個 JavaScript shell,如果你輸入的命令漏了 (),你會看到這個命令的源碼,拿到一個以 function (...){ 開頭的返回的內(nèi)容。

MongoDB 內(nèi)部用二進制序列化 JSON 格式,稱為 BSON。

如果要操作當前數(shù)據(jù)庫,用 db ,比如 db.help() 或者 db.stats()。

use 用來切換數(shù)據(jù)庫,比如,輸入 use learn。

大多數(shù)情況下我們會操作集合, 而不是數(shù)據(jù)庫。比如:用 db.COLLECTION_NAME ,比如 db.unicorns.help() 或者 db.unicorns.count()。

注意,除你指定的字段之外,會多出一個 _id 字段。每個文檔都會有一個唯一 _id 字段。你可以自己生成一個,或者讓 MongoDB 幫你生成一個 ObjectId 類型的。

2.2 插入數(shù)據(jù)

db.unicorns.insert({name: 'Aurora',
    gender: 'f', weight: 450})

2.3 刪除數(shù)據(jù)

db.unicorns.remove({})。

2.4 查詢

掌握選擇器(Selector):MongoDB 的查詢選擇器就像 SQL 語句里面的 where 一樣。

因此,你會在對集合的文檔做查找,計數(shù),更新,刪除的時候用到它。選擇器是一個 JSON 對象,最簡單的是就是用 {} 匹配所有的文檔。比如可以用 {gender:'f'}。

{field: value} 用來查找那些 field 的值等于 value 的文檔。 {field1: value1, field2: value2} 相當于 and 查詢。還有 lt,lte, gt,gte 和 $ne 被用來處理 小于,小于等于,大于,大于等于,和不等于操作。

db.unicorns.find({gender: 'm',
    weight: {$gt: 700}})

db.unicorns.find({gender: {$ne: 'f'},
    weight: {$gte: 701}})

2.5 判斷是否存在

$exists 用來匹配字段是否存在,比如:

db.unicorns.find({
vampires: {$exists: false}})

2.6 是否被包含用 $in

'$in' 被用來匹配查詢文檔在我們傳入的數(shù)組參數(shù)中是否存在匹配值,比如:

db.unicorns.find({
loves: {$in:['apple','orange']}})

2.7 邏輯操作中的 or,通過 $or 操作符 來操作。

使用 $or 操作符,再給它一個我們要匹配的數(shù)組:

db.unicorns.find({gender: 'f',
$or: [{loves: 'apple'},
      {weight: {$lt: 500}}]})

2.8 查詢 _id 字段

_id 字段生成的 ObjectId 可以這樣查詢:

db.unicorns.find(
{_id: ObjectId("TheObjectId")})

3. 更新數(shù)據(jù) (Update)

如果改變一個或者幾個字段的值的時候,你應該用 MongoDB 的 $set 操作。

$set 操作

db.unicorns.update({weight: 590}, {$set: {
name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
gender: 'm',
vampires: 99}})

直接用 update 會產(chǎn)生覆蓋效果,謹慎使用:

db.unicorns.update({name: 'Roooooodles'},
{weight: 590})

數(shù)值的增減

$inc 可以用來給一個字段增加一個正/負值

db.unicorns.update({name: 'Pilot'},
{$inc: {vampires: -2}})

增加新自動,用 $push

字段加一個值,通過 $push 操作:

db.unicorns.update({name: 'Aurora'},
{$push: {loves: 'sugar'}})

Upserts

update 語法還支持 upsert 更新,即:在文檔中找到匹配值時更新它,無匹配時向文檔插入新值。

要使用 upsert 我們需要向 update 寫入第三個參數(shù) {upsert:true},示例:

db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}}, {upsert:true});

db.hits.find();

批量 Updates時,multi 選項需要設為 true:

db.unicorns.update({},
{$set: {vaccinated: true }},
{multi:true});
db.unicorns.find({vaccinated: true});

4. 查詢

字段選擇

find 有第二個可選參數(shù),叫做 "projection"。這個參數(shù)是我們要檢索或者排除字段的列表。

db.unicorns.find({}, {name: 1});

默認的,_id 字段總是會返回的。我們可以通過這樣顯式的把它從返回結果中排除 {name:1, _id: 0}。

排序(Ordering)

sort 用于排序,我們指定我們希望排序的字段,以 JSON 方式,其中 1 表示升序 -1 表示降序。比如:

//heaviest unicorns first
db.unicorns.find().sort({weight: -1})

//by unicorn name then vampire kills:
db.unicorns.find().sort({name: 1,
    vampires: -1})

MongoDB 對未經(jīng)索引的字段進行排序是有大小限制的。如果你試圖對一個非常大的沒有經(jīng)過索引的結果集進行排序的話,你會得到個異常。

分頁(Paging)

對結果分頁可以通過 limitskip 游標方法來實現(xiàn)。比如:

db.unicorns.find()
    .sort({weight: -1})
    .limit(2)
    .skip(1)

通過 limitsort 的配合,可以在對非索引字段進行排序時避免引起問題。

count 計數(shù)

shell 中可以直接對一個集合執(zhí)行 count ,像這樣:

db.unicorns.count({vampires: {$gt: 50}})

實際上,count 是一個 cursor 的方法,shell 只是簡單的提供了一個快捷方式。

以不提供快捷方式的方法來執(zhí)行的時候需要這樣:

db.unicorns.find({vampires: {$gt: 50}})
    .count()

5. 數(shù)據(jù)建模

不支持 join

mongoDB 沒有 join (鏈接, 比如 內(nèi)連接inner Join,外連接out join)。傳統(tǒng)數(shù)據(jù)庫中的 join 基本上意味著不可擴展。就是說,如果想把數(shù)據(jù)水平擴展,你只能放棄在使用join。事實就是,數(shù)據(jù)之間的關系, 在 MongoDB 中無法直接表達和查詢。只能在我們的應用代碼中自己實現(xiàn),需要進行二次查詢 find ,把相關數(shù)據(jù)保存到另一個集合中。

示例:

先添加一個名叫 Leto 的 主管

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 負責管理的所有工人,只需要執(zhí)行:

db.employees.find({manager: ObjectId(
    "4d85c7039ab0fd70a117d730")})

數(shù)組

示例:

// 插入一個 manager 是單個對象。
db.employees.insert({_id: ObjectId(
    "4d85c7039ab0fd70a117d732"),
    name: 'Moneo',
    manager: ObjectId(
    "4d85c7039ab0fd70a117d730")});

// 插入一個 manager 是 數(shù)組。
db.employees.insert({_id: ObjectId(
    "4d85c7039ab0fd70a117d733"),
    name: 'Siona',
    manager: [ObjectId(
    "4d85c7039ab0fd70a117d730"),
    ObjectId(
    "4d85c7039ab0fd70a117d732")] })

注意上面的文檔,manager 字段的值既可以是單個對象,也可以是數(shù)組。而我們原來的 find 查詢依舊可用:

db.employees.find({manager: ObjectId(
    "4d85c7039ab0fd70a117d730")})

內(nèi)嵌文檔

MongoDB 還支持內(nèi)嵌文檔。來試試看向文檔插入一個內(nèi)嵌文檔,像這樣:

db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d734"),
name: 'Ghanima',
family: {mother: 'Chani',
    father: 'Paul',
    brother: ObjectId(
"4d85c7039ab0fd70a117d730")}})

像你猜的那樣,內(nèi)嵌文檔可以用 dot-notation 查詢:

db.employees.find({
'family.mother': 'Chani'})

反規(guī)范化(Denormalization)

一些對冗余處理 的討論。
并不是需要對你文檔里的每條數(shù)據(jù)都做冗余處理。而是說,與其對冗余數(shù)據(jù)心存恐懼,讓它影響你的設計決策,不如在建模的時候考慮什么信息應當屬于什么文檔。

假設你要寫一個論壇應用。

  • 傳統(tǒng)的方式是通過 posts 中的 userid 列,來關聯(lián)一個特定的 user 和一篇 post 。這樣的建模,在顯示 posts 的時候要查詢 (鏈接到) users。

  • 一個代替案是“增加冗余字段”,在每篇 post 中都冗余的多存儲 name 和 userid 兩個字段。這要用到內(nèi)嵌文檔,比如 user: {id: ObjectId('Something'), name: 'Leto'}。缺點是,如果用戶可以更新他們的名字,那將不得不對所有的文檔都進行更新。

其他選擇

記?。?一個獨立文檔的大小當前被限制在 16MB 。

處理一對多(one-to-many)或者多對多(many-to-many)場景的時候,id 數(shù)組通常是一個正確的選擇。

內(nèi)嵌文檔經(jīng)常使用的情形:大多數(shù)情況下多是很小的數(shù)據(jù)塊,面對總是被和父節(jié)點一起拉取的數(shù)據(jù)塊。

集合的規(guī)模討論:單個大而全?還是拆分小而專?

比如,常見的例子就是博客。你是應該分成一個 posts 集合和一個 comments 集合呢,還是應該每個 post 下面嵌入一個 comments 數(shù)組?

MongoDB 的處理方式:MongoDB 的靈活架構允許你把這兩種方式結合起來,你可以把評論放在獨立的集合中,同時在博客帖子下嵌入一小部分評論 (比如說最新評論) ,以便和帖子一同顯示。

這遵守以下的規(guī)則:“ 你到底想在一次查詢中獲取到什么內(nèi)容,那就怎么做?!?/p>

想一想,如果在關系型數(shù)據(jù)庫中,要把上面說的這兩種方式結合起來用,“要不要再建一個關聯(lián)表呢?”

6. MongoDB 適用場景

單一解決方案還是多技術方案?
對于許多項目來說 - 或者說大多數(shù) - 單一解決案是一個明智的選擇。只有你自己才知道,引進新技術是否利大于弊。引入MongoDB 往往不會完全替換舊的方案(比如用Mongo替換MySQL),而是說“不用再依賴單一的解決案來處理你的數(shù)據(jù)”,作為數(shù)據(jù)存儲的局部替代方案,是對你現(xiàn)有數(shù)據(jù)存儲方案能力的局部增強。

比如說用 Lucene 作為關系型數(shù)據(jù)庫的全文檢索索引的加強,或者用 Redis 作為持久型 key-value 存儲對緩存存儲的增強,MongoDB 就是用來保存你的數(shù)據(jù)能力的處理增強。

寫操作(Writes)

MongoDB 可以勝任的一個特殊角色是在日志領域。有兩點使得 MongoDB 的寫操作非常快。首先,你可以選擇發(fā)送了寫操作命令之后立刻返回,而無須等到操作完成。

如果想讓你的數(shù)據(jù) "過期" ,基于時間而不是整個集合的大小,你可以用 TTL 索引 ,所謂 TTL 是 "time-to-live" 的縮寫。

持久性(Durability)

從 2.0 版的 MongoDB 開始,日志是默認啟動的,該功能允許快速恢復服務器,比如遭遇到了服務器崩潰或者停電的情況。

事務(Transactions)

MongoDB 不支持事務。兩個代替案:

  • 第一個方案,就是各種原子更新操作。比如 inc 和set。還有像 findAndModify 命令,可以更新或刪除文檔之后,自動返回修改過的文檔
  • 第二個方案,當原子操作不能滿足的時候,回到兩段提交上來。

地理空間查詢(Geospatial)

一個很強大的功能就是 MongoDB 支持 geospatial 索引。這允許你保存 geoJSON 或者 x 和 y 坐標到文檔,并查詢文檔,用如 near 來獲取坐標集,或者within 來獲取一個矩形或圓中的點。

7. 聚合管道(Aggregation Pipeline)

聚合管道提供了一種方法用于轉換整合文檔到集合。你可以通過管道來傳遞文檔,就像 Unix 的 "pipe" 一樣,將一個命令的輸出傳遞到另第二個,第三個,等等

8. 性能和工具

索引(Index)

創(chuàng)建索引用 ensureIndex :

// where "name" is the field name
db.unicorns.ensureIndex({name: 1});

刪除索引用 dropIndex:

db.unicorns.dropIndex({name: 1});

可以創(chuàng)建唯一索引,這需要把第二個參數(shù) unique 設置為 true:

db.unicorns.ensureIndex({name: 1},
{unique: true});

索引可以內(nèi)嵌到字段中和任何數(shù)組字段。我們可以這樣創(chuàng)建復合索引:

db.unicorns.ensureIndex({name: 1,
vampires: -1});

Explain

需要檢查你的查詢是否用到了索引,你可以通過 explain 方法:

db.unicorns.find().explain()

復制(Replication)

MongoDB 的復制在某些方面和關系型數(shù)據(jù)庫的復制類似。所有的生產(chǎn)部署應該都是副本集,理想情況下,三個或者多個服務器都保持相同的數(shù)據(jù)。寫操作被發(fā)送到單個服務器,也即主服務器,然后從它異步復制到所有的從服務器上。你可以控制是否允許從服務器上進行讀操作,這可以讓一些特定的查詢從主服務器中分離出來,當然,存在讀取到舊數(shù)據(jù)的風險。如果主服務器異常關閉,從服務中的一個將會自動晉升為新的主服務器繼續(xù)工作。

分片(Sharding)

MongoDB 支持自動分片。分片是實現(xiàn)數(shù)據(jù)擴展的一種方法,依靠在跨服務器或者集群上進行數(shù)據(jù)分區(qū)來實現(xiàn)。一個最簡單的實現(xiàn)是把所有的用戶數(shù)據(jù),按照名字首字母 A-M 放在服務器 1 ,然后剩下的放在服務器 2。

狀態(tài)(Stats)

你可以通過 db.stats() 查詢數(shù)據(jù)庫的狀態(tài)。

分析器(Profiler)

可以這樣執(zhí)行 MongoDB profiler :

db.setProfilingLevel(2);

備份和還原

來備份我們的 learn 數(shù)據(jù)庫導 backup 文件夾,我們需要在控制臺或者終端中執(zhí)行執(zhí)行

mongodump --db learn --out backup

如果只還原 unicorns 集合,我們可以這樣做:

mongorestore --db learn --collection unicorns \
backup/learn/unicorns.bson

文件導入導入數(shù)據(jù)
mongoexport 和 mongoimport 是另外兩個可執(zhí)行文件,用于導出和從 JSON/CSV 格式文件導入數(shù)據(jù)。比如說,我們可以像這樣導出一個 JSON:

mongoexport --db learn --collection unicorns

CSV 格式是這樣:

mongoexport --db learn \
--collection unicorns \
--csv --fields name,weight,vampires

9. 參考

https://github.com/ilivebox/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容