MongoDB的併發

MongoDB的併發


線上環境遇到MongoDB的性能瓶頸,爲了解決性能瓶頸學習了一下MongoDB中的併發機制,記錄如下。下文中主要是對比了MongoDB 2.2和3.0.7這兩個版本的併發機制。

1. MongoDB鎖的類型

 在2.2版本中MongoDB用的是讀寫鎖,允許並行的讀但是隻能互斥的寫,當一個讀鎖存在的時候可以有多個讀操作共享這個鎖,但是當一個寫鎖存在的時候只能有一個寫操作獲得這個鎖,其它的讀或者寫不能共享這個鎖。
 在2.2版本中寫鎖是"貪婪"的,意味着寫比讀有更大的優先權,當一個讀和寫操作正在等待一個鎖的時候,MongoDB會優先滿足寫操作的鎖要求。
 在3.0版本中MongoDB的鎖機制就有了比較大的改進,跟常見的數據庫鎖機制比較相似了。3.0還是使用讀寫鎖機制,只是支持了多粒度的鎖,支持全局、數據庫、集合這幾個粒度的鎖(鎖的粒度待到下面章節來詳細瞭解)。MongoDB支持插件式的存儲引擎,這樣允許存儲引擎自己來實現比集合粒度更細的併發控制(例如在3.0版本中引入了WiredTiger引擎,這個引擎支持文檔粒度的鎖)。
 在3.0中除了"共享鎖S"(我個人理解就是讀鎖)和"互斥鎖X"(也就是寫鎖)以外還加入了"意向共享鎖IS"和"意向互斥鎖IX",這兩種類型的鎖預示我們後面需要更細粒度的鎖來讀寫資源。當我們用某一粒度的鎖以後所有比這個粒度大的地方都用"意向鎖"來鎖定。
 例如,當鎖定一個集合用於寫(使用X鎖)的時候,相應的數據庫鎖和全局鎖必須用意向鎖(IX鎖)來鎖定。一個數據庫能夠同時被ISIX模式鎖定,但是一個互斥鎖X不能和其它的鎖模式並存,一個共享鎖S只能和意向共享鎖IS同時存在。
 在3.0版本中鎖是公平的(不像2.2版本中寫鎖有更大的優先權),讀和寫順序在隊列中排隊。然而,爲了優化吞吐量,當一個請求被授權,其它兼容的請求都會在同一時刻被授權,這樣就可能在遇到矛盾的請求之前就已經釋放了這個請求。例如,剛剛一個X鎖被釋放,衝突隊列中包含了如下的鎖:

IS → IS → X → X → S → IS

 如果按照嚴格的先進先出(FIFO),只有最開始的兩個IS請求會被釋放,但是MongoDB會把所有跟IS兼容的ISS請求都同時釋放。一旦這些請求被釋放,MongoDB接着就會釋放X,即使有新的ISS請求到來,也就是說MongoDB只會釋放隊列中最前面的請求,這樣就不會有請求被"餓死"。

2. 鎖的粒度

 從2.2版本開始,MongoDB實現了數據庫級別的鎖,對於大部分的讀寫操作都用數據庫鎖即可,但是一些全局操作,通常涉及到多個數據庫操作的時候還是需要全局鎖。在2.2版本之前MongoDB只有全局鎖,例如,如果有6個數據庫那麼其中一個數據庫的寫操作不會影響其它5個數據庫的讀寫操作的,但是這在2.2之前是不行的。
 在MongoDB 3.0版本中鎖的粒度就變得更細了,除了全局鎖、數據庫鎖還加入了集合鎖,而且對於WiredTiger存儲引擎和MMAPv1存儲引擎而言兩者之間的鎖機制也有不同。
 WiredTiger:對於大部分的讀寫操作,WiredTiger使用樂觀鎖。WiredTiger對於全局、數據庫、集合級別只會使用意向鎖。當存儲引擎檢測到兩個操作之間的衝突,一個寫衝突導致MongoDB透明地重試寫操作。一些全局操作,跟2.2版本一樣還是會需要全局鎖,例如,刪除一個集合,那麼仍然還是需要一個互斥的數據庫鎖的。
 MMAPv1:3.0版本MMAPv1引擎用集合鎖,相比之前的版本數據庫鎖是最細粒度的鎖而言有了更進一步的改進。例如,在使用MMAPv1作爲存儲引擎的數據庫中有6個集合,當其中一個集合寫鎖存在的時候,其它5個集合仍然可以自由的使用讀鎖、寫鎖來進行讀寫操作。

3. 如何查看當前MongoDB鎖的狀態

 MongoDB提供瞭如下的命令來查看當前的鎖狀態:

 樓主一般用currentOp來查看當前MongoDB的鎖的狀態,具體的可以參考文檔。
 如果遇到一個慢查詢導致鎖一直沒釋放的可以參考我這篇文章MongoDB一次性能問題處理

4. 讀寫操作是否會主動讓出鎖?

 在某些情況下,讀寫操作會主動讓出自己擁有的鎖。
 長時間的讀或者寫操作,例如query、update、delete,都會在很多情況下主動讓出鎖。
 MongoDB的MMAPv1引擎使用啓發模式來預測要讀取的數據是否在內存中,如果預測數據沒有在內存中那麼在把數據從硬盤加載到內存的過程中這個讀鎖就會主動讓出,一旦數據加載完,這個讀操作就會重新獲得鎖。
 對於支持文檔級別併發控制的存儲引擎,如WiredTiger,當存在全局、數據庫、集合級別的意向鎖的時候沒有必要主動讓出鎖,因爲這些鎖並不會完全阻塞讀、寫操作。

5. 常見操作對應的鎖

 MongoDB中的一些常見的操作對應的鎖可以參考如下的兩個鏈接:
 - Which operations lock the database?
 - Which administrative commands lock the database?
 - Does a MongoDB operation ever lock more than one database?

6. sharding、replica set對併發的影響

 在sharding模式下每一個mongod實例都是獨立於分片集羣中其它實例的包括它的鎖,一個mongod實例中的鎖不會影響其它實例。
 在replica set模式下因爲要保持primary、secondaries之間的同步,所以當在primary寫入數據的時候MongoDB同步更新primary中的oplog(oplog是一個特殊的集合在local數據庫中),因此MongoDB會同時鎖住兩個數據庫以保證同步。

7. MongoDB支持事務嗎?

 MongoDB不支持多個文檔的事務。
 然而,MongoDB提供在單個文檔中的原子操作。通常情況下文檔級別的原子操作足夠解決在關係數據庫中要求ACID事務的大多數問題。
 例如,在MongoDB中你可以在一個文檔中把相關的數據嵌入嵌套數組或者嵌套文檔中,然後在一次原子操作中更新整個文檔。關係型數據庫可能會通過相關的幾個表或者行來達到這個目的,這樣的話就會需要事務來保證數據的原子性。
 SEE ALSOAtomicity and Transactions

8. MongoDB提供了什麼樣的隔離保證?

 MongoDB在並行讀寫的時候提供瞭如下的保證,MMAPv1或WiredTiger都會提供這些保證:
 1. 在單一的文檔中讀寫操作都是原子的,永遠不會把一個文檔置於不一致狀態。這個意味着一個讀者永遠不會看到一個部分內容更新的文檔,索引也會和集合內容一直保持一致。此外,在一個文檔中的一系列讀、寫操作都是串行的。
 2. 像db.collection.find()這種查詢謂詞只會返回匹配的文檔,db.collection.update()也只會更新匹配的文檔。(廢話)
 3. 對於一個排序的讀操作(例如,db.collection.find()db.collection.aggregate()),排序的順序並不會因爲併發的寫入而會被打亂。(不太理解)
 儘管在單個文檔操作中MongoDB提供了這些隔離保證,但是在程序執行期間可能會讀寫任意數量的文檔,對於這種多個文檔的讀寫MongoDB是沒有提供事務所以在併發寫的時候是不保證隔離的。這個意味着如下的一些情況可能會出現,無論是在MMAPv1或WiredTiger引擎中。
 1. Non-point-in-time read operations。這裏直接把原文給貼出來了,因爲我實在不知道該怎麼翻譯這個,我理解就是數據庫事務隔離中的"不可重複讀"。例如,在時間點t1開始讀取文檔(db.collection.find({"status_id":{$lt:20}})),一個更新文檔的操作在稍後的t2時刻發生(db.collection.update({"status_id":10},{"name":"andy"})),status_id = 10的文檔最後查找出來的結果可能會是更新後的文檔,也就是說MongoDB不能看到查找時刻的快照數據。這種情況在PG的"Repeatable reads (可重複讀)"事務隔離級別中就不會出現,PG最後的結果會是事務(也就是查詢開始之前)之前的快照數據,更新的內容不會出現在結果中。
 2. 非串行操作。假設我們在t1時刻讀取文檔d1,在稍後的t3時刻更新文檔d1,這種稱爲讀-寫依賴,如果我們的操作是串行,那麼讀操作就必須在寫操作之前被處理。再假設如果在t2時刻更新文檔t2,在稍後的t4時刻我們有一個對文檔t2的讀操作,這種稱爲寫-讀依賴,在串行調度中這就需要讀操作在寫操作之後。上面這兩種情況組合在一起就會導致循環依賴,這樣也就是導致了串行調度不可行。
 3. Dropped results。MongoDB在讀取文檔的時候可能會存在有部分匹配的文檔讀取失敗情況,因爲在這個過程中可能會有文檔被更新、刪除。但是隻要在查找過程中沒有被修改那麼肯定能讀取到匹配的文檔。說到底還是因爲沒有事務導致的。

9. 沒有提交到磁盤的更改能讀取到嗎?

 可以。對於那些還只存在於內存中沒有被持久化的更新是能夠被讀操作看到的,無論寫的關注級別和日誌配置是怎麼樣的。應用程序可能會看到如下的一些現象:
 1. 在一個寫操作還未回覆確認給客戶端的時候MongoDB允許併發的讀操作能夠看到這些寫操作的更新。關於不同寫關注級別對於的確認回覆可以參考:Write Concern
 2. 讀操作可能會看到部分可能會被回滾回去的數據在一些極端情況下,例如replica set故障或者斷電。但是這並不是意味着讀操作可以看到部分更新的文檔或者看到文檔的不一致狀態。這裏只是說一個文檔可能會被回滾回去,並不是說一個文檔的部分內容會回滾。
 其實這種情況就對應了數據庫事務隔離中的"read uncommitted"級別。

MongoDB 3.0.7 Concurrency
MongoDB 2.2 Concurrency

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章