用了一陣子mongodb,作一些小結,作為將來的參考。按照以往的習慣,先作一個總覽,然後再挑出一些自己比較關注的幾個點,作為珠璣,加以串聯闡述。
mongodb由C++寫就,其名字來自humongous這個單詞的中間部分,從名字可見其野心所在就是海量資料的處理。關於它的一個最簡潔描述為:scalable, high-performance, open source, schema-free, document-oriented database。我對於文檔型資料庫有一些個人的偏好,這種偏好是從半年前研究couchdb而來的,因為我覺得用它來描述一個具有個人化特徵的實體物件正合適,比如網站上的使用者或商品書籍之類的條目。
一些概念:
跟mysqld一樣,一個mongod服務可以有建立多個資料庫,每個資料庫可以有多張表,這裡的表名叫collection,每個collection可以存放多個文檔(document),每個文檔都以BSON(binary json)的形式存放於硬碟中。跟關係型資料庫不一樣的地方是,它是的以單文檔為單位儲存的,你可以任意給一個或一批文檔新增或刪除欄位,而不會對其它文檔造成影響,這就是所謂的schema-free,這也是文檔型資料庫最主要的優點。跟一般的key-value資料庫不一樣的是,它的value中儲存了結構資訊,所以你又可以像關係型資料庫那樣對某些域進行讀寫、統計等操作。可以說是兼備了key-value資料庫的方便高效與關係型資料庫的強大功能。
索引
跟關係型資料庫類似,mongodb可以對某個欄位建立索引,可以建立複合式索引、唯一索引,也可以刪除索引。當然建立索引就意味著增加空間開銷,我的建議是,如果你能把一個文檔作為一個對象的來考慮,線上上應用中,你通常只要對對象ID建立一個索引即可,根據ID取出對象某些資料放在memcache即可。如果是背景分析需要,響應要求不高,查詢非索引的欄位即便直接掃表也費不了太多時間。如果還受不了,就再建一個索引得了。
預設情況下每個表都會有一個唯一索引:_id,如果插入資料時沒有指定_id,服務會自動產生一個_id,為了充分利用已有索引,減少空間開銷,最好是自己指定一個unique的key為_id,通常用對象的ID比較合適,比如商品的ID。
capped collection
capped collection是一種特殊的表,它的建表命令為:
db.createCollection("mycoll", {capped:true, size:100000})
允許在建表之初就指定一定的空間大小,接下來的插入操作會不斷地按順序APPEND資料在這個預分配好空間的檔案中,如果已經超出空間大小,則回到檔案頭覆蓋原來的資料繼續插入。這種結構保證了插入和查詢的高效性,它不允許刪除單個記錄,更新的也有限制:不能超過原有記錄的大小。這種表效率很高,它適用於一些暫時儲存資料的場合,比如網站中登入使用者的session資訊,又比如一些程式的監控日誌,都是屬於過了一定的時間就可以被覆蓋的資料。
複製與分區
mongodb的複製架構跟mysql也很類似,除了包括master-slave構型和master-master構型之外,還有一個Replica pairs構型,這種構型在平常可以像master-slave那樣工作,一但master出現問題,應用會自動了串連slave。要做複製也很簡單,我自己使用過master-slave構型,只要在某一個服務啟動時加上–master參數,而另一個服務加上–slave與–source參數,即可實現同步。
分區是個很頭疼的問題,資料量大了肯定要分區,mysql下的分區正是成為無數DBA的噩夢。在mongodb下,文檔資料庫類似key-value資料庫那樣的易分布特性就顯現出來了,無論構造分區服務,新增節點還是刪除節點都非常容易實現。但mongodb在這方面做還不足夠成熟,現在分區的工作還只做到alpha2版本(mongodb v1.1),估計還有很多問題要解決,所以只能期待,就不多說了。
效能
在我的使用場合下,千萬層級的文檔對象,近10G的資料,對有索引的ID的查詢不會比mysql慢,而對停用字詞段的查詢,則是全面勝出。mysql實際無法勝任大資料量下任意欄位的查詢,而mongodb的查詢效能實在讓我驚訝。寫入效能同樣很令人滿意,同樣寫入百萬層級的資料,mongodb比我以前試用過的couchdb要快得多,基本10分鐘以下可以解決。補上一句,觀察過程中mongodb都遠算不上是CPU殺手。
GridFS
gridfs是mongodb一個很有趣的類似檔案系統的東西,它可以用一大塊檔案空間來存放大量的小檔案,這個對於儲存web2.0網站中常見的大量小檔案(如大量的帳戶圖片)特別有效。使用起來也很方便,基本上跟一般的檔案系統類似。
用合適的資料庫做適合的事情
mongodb的文檔裡提到的user case包括即時分析、logging、全文檢索搜尋,國內也有人使用mongodb來儲存分析網站日誌,但我認為mongodb用來處理有一定規模的網站日誌其實並不合適,最主要的就是它占空間過於虛高,原來1G的日誌資料它可以存成幾個G,如此下去,一個硬碟也存不了幾天的日誌。另一方面,資料量大了肯定要考慮sharding,而mongodb的sharding到現在為止仍不太成熟。由於日誌的不可更新性的,往往只需APPEND即可,又因為對日誌的操作往往只集中於一兩列,所以最合適作為日誌分析的還是列儲存型的資料庫,特別是像infobright那樣的為資料倉儲而設計的列儲存資料庫。
由於mongodb不支援事務操作,所以事務要求嚴格的系統(如果銀行系統)肯定不能用它。
mongodb佔用空間過大的原因,在官方的FAQ中,提到有如下幾個方面:
1、空間的預分配:為避免形成過多的硬碟片段,mongodb每次空間不足時都會申請產生一大塊的硬碟空間,而且申請的量從64M、128M、256M那樣的指數遞增,直到2G為單個檔案的最大體積。隨著資料量的增加,你可以在其資料目錄裡看到這些整塊產生容量不斷遞增的檔案。
2、欄位名所佔用的空間:為了保持每個記錄內的結構資訊用於查詢,mongodb需要把每個欄位的key-value都以BSON的形式儲存,如果value域相對於key域並不大,比如存放數值型的資料,則資料的overhead是最大的。一種減少空間佔用的方法是把欄位名盡量取短一些,這樣佔用空間就小了,但這就要求在易讀性與空間佔用上作為權衡了。我曾建議作者把欄位名作個index,每個欄位名用一個位元組表示,這樣就不用擔心欄位名取多長了。但作者的擔憂也不無道理,這種索引方式需要每次查詢得到結果後把索引值跟原值作一個替換,再發送到用戶端,這個替換也是挺耗費時間的。現在的實現算是拿空間來換取時間吧。
3、刪除記錄不釋放空間:這很容易理解,為避免記錄刪除後的資料的大規模挪動,原記錄空間不刪除,只標記“已刪除”即可,以後還可以重複利用。
4、可以定期運行db.repairDatabase()來整理記錄,但這個過程會比較緩慢。
因為官方文檔中對各方面的內容已經有很詳細的敘述,所以我並沒有再過多的引用原文與代碼,只是結合自己的使用歸納一些心得,有興趣的朋友不妨直接去翻文檔中自己感興趣的問題,超群的部落格上有一個很好的入門介紹。
最後總結一句,文檔型資料庫有點像波粒二象性,總能在適當的時候表現出它作為關係型資料庫或key-value資料庫的優勢來。
實戰案例:
昨天我訪問mongodb的python程式開始出錯,經常拋出AssertionError異常,經查證只是master查詢異常,slave正常,可判斷為master的資料出了問題。
修複過程:
1、在master做db.repairDatabase(),不起作用;
2、停止slave的同步;
3、對slave作mongodump,備份資料;
4、對master作mongostore,把備份資料恢複,使用–drop參數可以先把原表刪除。
5、恢複slave的同步。