MongoDB 文檔閱讀筆記

NoSQL 資料庫在上年炒得很熱,於是我也萌生了使用 NoSQL 資料庫寫一個應用的想法。首先來認識一下 NoSQL。NoSQL 是一個縮寫,含義從最初的 No-SQL 到現在已經成為了 Not-Only-SQL。確實後面一種解釋比較符合 NoSQL 的使用場景。

現在網路上被人所知的 NoSQL 資料庫可以在這個網頁(http://nosql-database.org)看到。這個列表林林總總一大堆,要選擇哪個資料庫入手呢?

1. 選擇非關聯資料庫

在我關注的 Web 領域,特別是 Ruby on Rails 社區,比較多提到的是這幾個資料庫:

  • Cassandra , apache基金會下的非關聯資料庫。早前一段時間傳言 Twitter 要用 Cassandra 替代 Mysql,一時間坊間流傳“NoSQL 要革 SQL 的命了!”。不過 Twitter 博客澄清,Twitter 只是在部分領域使用 Cassandra,存放 Tweets 的主資料庫依然是 MySQL。
  • MongoDB,10gen 公司的開源非關聯資料庫產品,可以選擇他們公司的商業支持。RoR 相關的插件挺多。
  • CouchDB,另一個apache基金會下的非關聯資料庫。
  • Redis,特點是運行在記憶體中,速度很快。相比于用來持久化資料,也許更接近於 memcached 這樣的緩存系統,或者用來實現任務佇列。(比如resque)

在這些候選名單中我選擇了 MongoDB。因為它最近在 RoR 社區中的露臉率比較高,網頁文檔完善,並且專案主頁的設計也不錯 : P。

在陳述 MongoDB 的特性之前,還是給第一次接觸 NoSQL 的人提個醒:不要意圖用 NoSQL 全盤取代 SQL 資料庫。非關聯資料庫的出現不是為了取代關聯資料庫。具體的說,MongoDB 並不支援複雜的事務,只支援少量的原子操作,所以不適用於“轉帳”等對事務和一致性要求很高的場合。而 MongoDB 適合什麼場合,請繼續閱讀。

2. 文檔型資料庫初探

關聯資料庫比如 MySQL,通常將不同的資料劃分為一個個“表”,表的資料是按照“行”來儲存的。而關聯資料庫的“關係”是指通過“外鍵”將表間或者表內的資料關聯起來。比如 文章-評論 的一對多關係可以用這樣的表來實現:

posts(id, author_id, content, ... )
comments(id, name, email, web_site, content, post_id)

實現關聯的關鍵就是 comments 表的最後一個 post_id 欄位,將 comment 資料的 post_id 欄位設為評論目標文章的 id 值,就可以用 SQL 語句進行相關查詢(假設要查的文章 id 是 1):

SELECT * FROM comments WHERE post_id = 1;

相對於關聯資料庫的行式儲存和查詢,MongoDB 作為一個文檔型資料庫,可以支援更具層次感的資料。上面舉的 文章-評論 結構,在 MongoDB 裏面可以這樣設計。

{
  _id : ObjectId(...),
  author : 'Rei',
  content : 'content text',
  comments : [ { name : 'Asuka'
                 email : '...',
                 web_site : '...',
                 content : 'comment text'} ]
}

comments 項是內嵌在 post 項中的(作為陣列)。在 MongoDB 中,一個資料項目叫做 Document,一個文檔嵌入另一個文檔(comment 嵌入 post)叫做 Embed,儲存一系列文檔的地方叫做 Collections。順便一提,MongoDB 中也提供類似 SQL 資料庫中的表間關聯,叫做 Reference。

3. 用文檔型資料庫儲存文檔

可以看到,文檔性資料庫從儲存的資料項目上就跟 SQL 資料庫不同。在 MongoDB 中,文檔是以BSON 格式(類似 JSON)儲存的,可以支援豐富的層次的結構。由於資料結構的表達能力更強,用 MongoDB 儲存文檔型資料可以比 SQL 資料庫更直觀和高效。

3.1 簡化模式設計

在 SQL 資料庫中,為表達資料的從屬關係,通常要將表間關係分為 one-to-one,one-to-many,many-to-many 等模式進行設計,這通常會需要很多鏈結表的輔助。在 MongoDB 中,如果關聯文檔體積較小,固定不變,並且與另一文檔是主從關係,那麼通常可以嵌入(Embed)主文檔。

常見情景:評論、投票點擊數據、Tag。

這類場景的有時單個資料項目體積很小,但是數量巨大,與之相應的是查詢成本也會上升。如果將這些小資料嵌入所屬文檔,在查詢主文檔時一併提取,查詢效率要比 SQL 高,後者通常需要開支較大的 JOIN 查詢。並且根據文檔介紹,每個文檔包括 Embed 部分在物理硬碟上都是儲存在同一區域的,IO 部分也會比 SQL 資料庫快。(注,MongoDB 有單文檔大小限制)

3.2 動態的文檔模式

MongoDB 中的文檔其實是沒有模式的,不像 SQL 資料庫那樣在使用前強制定義一個表的每個欄位。這樣可以避免對空欄位的無謂開銷。

例如兩個用戶的聯繫資訊,A 用戶帶有 email 不帶 url,B 用戶帶有 url 不帶 email。在 SQL 資料庫中需要為兩個資料項目都提供 email 段和 url 段,而在 MongoDB 中可以這樣保存:

[ { name : 'A', email : 'A email address' }, { :name : 'B', url : 'B url address' } ]

4. MongoDB 另外一些特點

4.1 JSON 文檔式查詢

MongoDB 的查詢語言看起來是這樣的:

> db.users.find( { x : 3, y : "abc" } ).sort({x:1});

這是在 MongoDB 內置的 JavaScript 控制臺上的查詢,表示在名為 users 的 Collections 中查找 x = 3,y = “abc” 的文檔,並且以 x 遞增的順序返回資料。

JSON 文檔式查詢會讓寫慣應用層代碼的開發者眼前一亮,但對於精通 SQL 查詢的關聯資料庫管理員來說就是一個新的挑戰了。

4.2 對分散式的支援

MongoDB 對大型網站的最大吸引力也許來源於其對分散式部署的支持。當今互聯網最流行的資料庫 MySQL 在網站擴大到一定規模之後就會遇到擴展瓶頸,解決方案通常是分表分庫、配置主從資料庫。很多互聯網開拓者前仆後繼的為資料庫擴展性奮鬥,留下了一頁頁的寶貴經驗。

既然年復一年的有人為同一個問題奮鬥,為什麼不將這些問題在資料庫層面就解決了呢?而 MongoDB 的優勢之一就是內建對分散式的支持。

不過我沒有組建資料庫群集的需求,所以還未閱讀這方面的文檔。

總結

沒有銀彈,這個教誨已經提過太多。由於缺乏對事務的支援,MongoDB 不太適用于金融等行業的關鍵部分(我想也沒有這個人群來看我博客吧)。但對於現在要應對海量細資訊的 web 網站來說,MongoDB 可能恰好出現在了正確的時代。NoSQL 的無模式,能讓網站開發的迭代更輕盈;MongoDB 對分散式的支援,可以緩解網站快速成長時在資料庫端的瓶頸疼痛。

不要激進的用 NoSQL 替代所有 MySQL 應用,激進的 NoSQL 化只會像5年前的 all rewrite by RoR 浪潮一樣,耗費不必要的精力。但在新專案開發之初,可以考慮是否更適合使用 NoSQL。而我,已經打算在下一個 web 專案裏面使用 MongoDB。