開始使用MongoDB之前應該知道的14件事
本文要點
我從事資料庫相關工作已經很長時間了,但是最近才開始使用MongoDB。在開始使用MongoDB之前,我希望有些事情我已經知道。根據一般經驗,對於資料庫是什麼以及它們能幹什麼,人們會有先入為主的認識。為了給他人提供方便,本文列出了一些常見的錯誤。
建立一個無需身份驗證的MongoDB伺服器
很遺憾,MongoDB在安裝時預設不啟用身份驗證。在只從本地訪問的工作站上,這沒什麼不好。但是,由於MongoDB是一個多租戶系統,它會盡可能地佔用記憶體,因此最好是安裝在伺服器上,最大限度地提供記憶體,即使是開發工作。在伺服器上使用預設埠安裝而不啟用身份驗證是在自找麻煩,尤其是可以在查詢中執行任意JavaScript時(例如把$whereofollow,noindex" target="_blank">作為注入攻擊的載體 )。
身份驗證方法有多種,但是使用者ID/密碼憑證最容易安裝和管理。當你考慮基於LDAP/">LDAP的身份驗證 時,可以採用那個方法。在我們談論安全時,MongoDB必須保持最新,而且,在日誌裡查詢未授權訪問的跡象總是值得的。我不喜歡使用預設埠。
忘記限制MongoDB的攻擊面
MongoDB的安全檢查清單 為降低網路滲透和資料洩露風險提供了很好的建議。我們很容易會認為,開發伺服器不需要高等級的安全。不是這樣的:安全對於所有MongoDB伺服器都很重要。尤其是,除非有非常好的理由要使用mapReduce 、group 或$where ,否則你應該在配置檔案中設定javascriptEnabled:false ,禁用JavaScript。因為標準MongoDB的資料檔案是不加密的,另外,使用專門的使用者執行MongoDB 也是一個明智的做法,對資料檔案的完全訪問僅限於那個使用者,這樣就可以使用作業系統自帶的檔案訪問控制了。
沒有設計一個模式
對於模式,MongoDB沒有強制要求。這不是說它不需要模式。如果你真想儲存文件而又沒有一致的模式,那麼你可以非常快速、簡單地儲存它們,但是檢索會十分麻煩 。
“MongoDB模式設計的六大經驗原則 ”是一篇值得一讀的經典文章,而第三方工具(如Studio 3T)提供的類似“模式瀏覽器(Schema Explorer )”這樣可以執行定期模式檢查的特性也是值得擁有的。
忘記排序規則(排序順序)
這比其他任何的配置錯誤都會導致更多的挫折和時間浪費。MongoDB預設使用二進位制排序規則 。這對任何地方的文化都是不利的。在80年代,大小寫敏感、重音敏感、二進位制排序規則,和念珠、土耳其長衫和卷鬍子一起,被視為奇怪的時代錯誤。現在,他們沒法辯解了。在現實生活中,motorbike和Motorbike就是一樣,而Britain和britain就是同一個地方。小寫字母和大寫字母只是書寫上的等價。就不要讓我再說重音字元排序規則了。當你建立一個MongoDB資料庫時,使用一種合乎系統使用者語言和文化 的重音敏感、大小寫敏感 排序規則。這使得字串資料的檢索容易許多。
建立大文件集合
MongoDB樂於把最大16MB的文件置於集合中,而GridFS 設計用於超過16MB的大文件。但是,可以容納大文件並不意味著那是一個好主意。MongoDB在單個文件的大小為幾KB時表現最好,處理它們的方式更像寬SQL表的行。大文件會導致多種效能問題 。
使用大陣列建立文件
文件可以包含陣列。最好是把陣列元素的數量保持在四位數以下。如果陣列頻繁新增,會使得包含它的文件過大,那樣,它在磁碟上的位置就需要移動 ,反過來,這意味著每個索引都必須更新 。當一個包含大陣列的文件重新索引時,由於每個陣列元素都有一個單獨的索引條目 ,所以會發生大量的索引重寫。此外,這種重新索引在這類文件插入或刪除時也會發生。
為了最小化這個問題,MongoDB有一個“填充因子(padding factor )”,為文件增長提供空間。
你也許會想,你可以通過不建立陣列索引來繞開這個問題。遺憾的是,沒有索引,你會遇到其他問題。因為文件會從頭到尾掃描,找到一個接近陣列尾部的元素需要花更多的時間,大部分處理這個文件的操作都會變慢 。
忘記聚合情況下的階段排序
在有查詢優化器的資料庫系統中,你編寫的查詢是說明你想要什麼而不是如何獲取它。這就像在餐館中點餐;你通常只需要點菜,而不必對廚師發出詳細的指令。
在MongoDB中,你是對廚師發指令。例如,你需要通過$match和$project確保管道中的資料儘早減少,排序只在資料減少時發生一次,查詢按照你希望的順序執行。查詢優化器省去了不必要的工作,優化階段順序,選擇連線型別,這會把你寵壞。MongoDB給了你更多的控制,但這種便利是有成本的。
像Studio 3T 這樣的工具使構建準確的MongoDB聚合查詢變得更容易。它的聚合編輯器特性使你可以一次對一個階段應用管道操作符,你可以在每個階段驗證輸入和輸出,更便於除錯。
使用快速寫
永遠不要把MongoDB設為低穩定性的高速寫。看上去,“file-and-forget”模式使得寫入速度變快了,因為命令在實際寫入任何東西前就返回了。如果系統在資料寫入磁碟之前崩潰了,就會丟失,存在出現不一致狀態的風險。所幸,64位的MongoDB啟用了“日誌(Journaling)”。
MMAPv1和WiredTiger儲存引擎都使用日誌預防上述情況,不過,在日誌關閉的情況下,WiredTiger也可以在還原過程中恢復到最後一致的檢查點 。
日誌可以確保資料庫在恢復時處於一致狀態,它會儲存日誌寫入時的所有資料。日誌寫入的時間間隔可以使用執行時選項commitIntervalMs 來配置。
為了確保寫入,就要確保在配置檔案中啟用日誌(storage.journal.enabled) ,而且提交間隔要和你能夠承擔的資料丟失相對應。
無索引排序
在搜尋和聚合中,你經常希望排序資料。但願那是在最後階段完成的,在結果過濾之後,從而減少需要排序的資料量。即使在那個時候,你需要一個可以覆蓋排序的索引 。單鍵索引或混合索引都可以。
當沒有合適的索引可用時,MongoDB就不得不在沒有索引的情況下排序。對於排序操作中所有文件的總大小,Operations" rel="nofollow,noindex" target="_blank">有32MB的記憶體限制 ,如果MongoDB達到了這個限值,它就會產生錯誤,或者有時候僅僅返回一個空的記錄集 。
Lookup而沒有索引支援
Lookup的功能和SQL聯合查詢類似。為了獲得良好的效能,作為外來鍵的鍵值上需要有索引。這並不明顯,因為其使用並沒有在explain()中報告。這些索引並不包含在explain()記錄的索引裡,那些索引是供管道操作符$match、$sort出現在管道開始時使用的。現在,索引可以覆蓋聚合管道的任何階段 。
不使用多條更新
db.collection.update() 方法用於修改一個已存在文件的一部分或全部,或者是整個替換一個已存在的文件,這取決於你提供的更新引數 。除非你設定multi引數,更新匹配查詢條件的所有文件,否則它不會更新集合裡的所有文件。這一點不是那麼明顯。
忘記雜湊物件中鍵序的意義
在JSON中,一個物件包含一個無序集合,而該集合中有零個或多個名/值對,其中名是一個字串,而值是一個字串、數值、布林值、空、物件或陣列。
遺憾的是,BSON在做搜尋時給順序賦予了意義。在MongoDB中,嵌入物件中鍵的順序很重要 ,也就是說,{ firstname: "Phil", surname: "factor" }和{ surname: "factor", firstname: "Phil" }就不匹配。這意味著,你必須保留文件中名/值對的順序,如果你想確保可以找到它們的話。
混淆“null”和“undefined”
根據正式的JSON標準 (ECMA-404第5節),“undefined”值在JSON中從來就是不合法的,雖然它事實上已經在JavaScript中使用。而且,它在BSON中是“deprecated”,會轉換成$null,這並不是一個總令人滿意的解決方案。在MongoDB中,要避免使用“undefined” 。
使用$limit()而未用$sort()
通常,當你在MongoDB中開發時,僅僅檢視查詢或聚合返回的結果的樣例會很有用。 $limit()就是為了滿足這個要求,但是,它永遠不應該出現在最終版本的程式碼中,除非你首先使用了$sort。這是因為,不這樣的話,你就無法保證結果的順序,你就無法可靠地“按頁瀏覽”資料。為了確保可靠性,查詢或聚合必須是“確定的”,就是說,它們每次執行都會給出相同的結果。包含$limit而不包含$sort的程式碼不是確定的,後續會導致難以跟蹤的Bug。
小結
對於MongoDB,讓你最終感到失望的唯一方式是把它直接和另一種型別的資料庫如RDBMS比較,或者對它有特別的期待。這就像把桔子和叉子比較。資料庫系統有它們的用途。最好是理解並領會這些差別。強迫MongoDB開發人員按照RDBMS的方式做事就太遺憾了,我希望繼續看到解決舊問題的有趣的新方法,如確保資料完整性、使資料系統具有從故障和惡意破壞中恢復的能力。
在4.0版本中,MongoDB引入了ACID事務處理,這是以創新方式引入重大改善的一個很好的例子。多文件、多語句事務現在是原子的了,它允許開發人員調整用於獲取鎖的時間,過期掛起事務以及修改隔離級別。
關於作者
Phil Factor(為保護作者隱去真名),又稱資料庫摩爾,他有將近四十年的資料庫密集型應用程式經驗。在20世紀80年代初的一次展覽會上,憤怒的比爾蓋茨曾對他大吼大叫,自此,在整個職業生涯中,他就堅決匿名。
檢視英文原文:14 Things I Wish I’d Known When Starting with MongoDB