MongoDB 那些坑


MongoDB 是目前炙手可熱的 NoSQL 文檔型數據庫,它提供的一些特性很棒:如自動 failover 機制,自動 sharding,無模式 schemaless,大部分情況下性能也很棒。但是薄荷在深入使用 MongoDB 過程中,遇到了不少問題,下面總結幾個我們遇到的坑。特別申明:我們目前用的 MongoDB 版本是 2.4.10,曾經升級到 MongoDB 2.6.0 版本,問題依然存在,又回退到 2.4.10 版本。

MongoDB 數據庫級鎖

坑爹指數:5星(最高5星)

MongoDB的鎖機制和一般關系數據庫如 MySQL(InnoDB), Oracle 有很大的差異,InnoDB 和 Oracle 能提供行級粒度鎖,而 MongoDB 只能提供 庫級粒度鎖,這意味着當 MongoDB 一個寫鎖處於占用狀態時,其它的讀寫操作都得干等。

初看起來庫級鎖在大並發環境下有嚴重的問題,但是 MongoDB 依然能夠保持大並發量和高性能,這是因為 MongoDB 的鎖粒度雖然很粗放,但是在鎖處理機制和關系數據庫鎖有很大差異,主要表現在:

  • MongoDB 沒有完整事務支持,操作原子性只到單個 document 級別,所以通常操作粒度比較小;
  • MongoDB 鎖實際占用時間是內存數據計算和變更時間,通常很快;
  • MongoDB 鎖有一種臨時放棄機制,當出現需要等待慢速 IO 讀寫數據時,可以先臨時放棄,等 IO 完成之后再重新獲取鎖。

通常不出問題不等於沒有問題,如果數據操作不當,依然會導致長時間占用寫鎖,比如下面提到的前台建索引操作,當出現這種情況的時候,整個數據庫就處於完全阻塞狀態,無法進行任何讀寫操作,情況十分嚴重。

解決問題的方法,盡量避免長時間占用寫鎖操作,如果有一些集合操作實在難以避免,可以考慮把這個集合放到一個單獨的 MongoDB 庫里,因為 MongoDB 不同庫鎖是相互隔離的,分離集合可以避免某一個集合操作引發全局阻塞問題。

建索引導致數據庫阻塞

坑爹指數:3星

上面提到了 MongoDB 庫級鎖的問題,建索引就是一個容易引起長時間寫鎖的問題,MongoDB 在前台建索引時需要占用一個寫鎖(而且不會臨時放棄),如果集合的數據量很大,建索引通常要花比較長時間,特別容易引起問題。

解決的方法很簡單,MongoDB 提供了兩種建索引的訪問,一種是 background 方式,不需要長時間占用寫鎖,另一種是非 background 方式,需要長時間占用鎖。使用 background 方式就可以解決問題。
例如,為超大表 posts 建立索引,
千萬不用使用

db.posts.ensureIndex({user_id: 1}) 

而應該使用

db.posts.ensureIndex({user_id: 1}, {background: 1}) 

不合理使用嵌入 embed document

坑爹指數:5星

embed document 是 MongoDB 相比關系數據庫差異明顯的一個地方,可以在某一個 document 中嵌入其它子 document,這樣可以在父子 document 保持在單一 collection 中,檢索修改比較方便。

比如薄荷的應用情景中有一個 Group document,用戶申請加入 Group 建模為 GroupRequest document,我們最初的時候使用 embed 方式把 GroupRequest 放置到 Group 中。
Ruby 代碼如下所示(使用了 Mongoid ORM):

class Group include Mongoid::Document ... embeds_many :group_requests ... end class GroupRequest include Mongoid::Document ... embedded_in :group ... end 

這個使用方式讓我們掉到坑里了,差點就爬不出來,它導致有接近兩周的時間系統問題,高峰時段常有幾分鍾的系統卡頓,最嚴重一次甚至引起 MongoDB 宕機。

仔細分析后,發現某些活躍的 Group 的 group_requests 增加(當有新申請時)和更改(當通過或拒絕用戶申請時)異常頻繁,而這些操作經常長時間占用寫鎖,導致整個數據庫阻塞。原因是當有增加 group_request 操作時,Group 預分配的空間不夠,需要重新分配空間(內存和硬盤都需要),耗時較長,另外 Group 上建的索引很多,移動 Group 位置導致大量索引更新操作也很耗時,綜合起來引起了長時間占用鎖問題。

解決問題的方法,說起來也簡單,就是把 embed 關聯更改成的普通外鍵關聯,就是類似關系數據庫的做法,這樣 group_request 增加或修改都只發生在 GroupRequest 上,簡單快速,避免長時間占用寫鎖問題。當關聯對象的數據不固定或者經常發生變化時,一定要避免使用 embed 關聯,不然會死的很慘。

不合理使用 Array 字段

坑爹指數:4星

MongoDB 的 Array 字段是比較獨特的一個特性,它可以在單個 document 里存儲一些簡單的一對多關系。

薄荷有一個應用情景使用遇到嚴重的性能問題,直接上代碼如下所示:

class User include Mongoid::Document ... field :follower_user_ids, type: Array, default: [] ... end 

User 中通過一個 Array 類型字段 follower_user_ids 保存用戶關注的人的 id,用戶關注的人從 10個到 3000 個不等,變化是比較頻繁的,和上面 embed 引發的問題類似,頻繁的 follower_user_ids 增加修改操作導致大量長時間數據庫寫鎖,從而引發 MongoDB 數據庫性能急劇下降。

解決問題的方法:我們把 follower_user_ids 轉移到了內存數據庫 redis 中,避免了頻繁更改 MongoDB 中的 User, 從而徹底解決問題。如果不使用 redis,也可以建立一個 UserFollower 集合,使用外鍵形式關聯。

先列舉上面幾個坑吧,都是害人不淺的陷阱,使用 MongoDB 過程一定要多加注意,避免掉到坑里。

 

針對使用中遇到的問題,談點我自己的感受吧,就談點注意事項而已。

1. 一定要合理創建索引, 有很多人都被宣傳片迷惑,認為mongo的讀取速度本身就應該很快,所以從mysql轉過來后,就連創建索引都忘了,當表(collection) 很大時,不創建索引是非常影響性能的。 創建索引很簡單,如果你不想使用shell那么麻煩,直接在model里面聲明就是了:index({ xxx: 1 }, { unique: true, background: true });然后運行一個rake命令:rake db:mongoid:create_indexes 就ok了,這個命令不會重復創建的。

2. 大表查詢時,只返回你想要的列,樓主講了很多write的性能問題,可能是場景不同的原因,我們大量遇到了查詢的性能問題;這一點就不用多說了吧,其他關系型數據庫也有這種問題。 特別是單collection字段數據量比較大時,非常容易引起性能問題,在rails里面也很簡單,查詢時加上only就是了。比如 User.where(xxx).only(:f1,:f2) 。

3. 盡量一次返回所有需要的數據,避免GET_MORE,避免游標操作,當用戶進行查詢迭代時,mongo會首先返回一個數據塊供你迭代,當你迭代的數據超過這個數據塊時,mongoid 發起 GET_MORE 命令移動游標獲取下一個數據塊,而就是這個移動游標的操作就非常慢,特別是你返回的列比較多的時候,性能非常低。每次返回的數據塊的大小是由batchSize控制的,可以通過修改它的默認值進行控制。

4. 盡量避免在model里面使用Array類型的字段,原因樓主已經說了,不過我們遇到的還是查詢的問題,因為你使用了Array,查詢時,你不可避免的會使用 ##in## 操作,in操作無法利用索引,這個在關系型數據庫里面也是存在的,大表操作一定要避免。

5. 不要在和數據庫直接相關的model里面使用繼承, 什么意思呢?就是 modelB < model A ,而他們都是mongo里面的 document,為什么不能這樣? 因為mongoid的內部實現其實只會創建一張表就是documentA, 然后在 documentA 里面用一個 _type 字段來標識 documentB,這樣當你查詢 modelB 時,內部會生成一個查詢到 documentA 的語句,那個查詢就是用的 _type in [xxxx] 類似這樣的語句,你看又是 in 操作。如果這種情況你是在后期才發現的,你真是回天無術,想死的心都有:)。

6. 事務,還是事務,mongodb不支持事務,所以你一定要考慮清楚,權衡利弊。我們有些功能就必須使用事務,沒辦法,我想到一個非常丑陋的方法,記錄每個創建和更新的model,它的id和更新數據,如果一旦有異常,我就撤銷更新和創建,真的是非常麻煩。想想看在一個支持事務的關系型數據庫里面,這些是非常簡單的。

7. 主從備份還不是很成熟,這一點,估計是我研究的不深入的原因,我仍然認為主從備份不是很成熟,有些時候簡直就是提心吊膽,如果有經驗的同學在這里,可以多多討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM