mongodb mysql并发_MongoDB:锁和并发控制
MongoDB:鎖和并發控制
本文主要介紹兩部分內容,第一部分是MongoDB的鎖,第二部分是MongoDB的并發控制。這都跟MongoDB性能和效率相關的,有助于在使用MongoDB過程中,知道哪些操作會帶來怎樣效率的影響。第二部分從業務層面出發介紹并發控制的方法,基本上都是開發中碰到的常見場景。
MongoDB鎖
###鎖類型
Mongodb使用的是read-write讀寫鎖,允許多個讀者并發的加共享鎖訪問同一資源,而同一時間只允許一個寫者加排它鎖改變資源,此時不允許其它的讀和寫。
在mongodb中,還使用了意向鎖(IS、IX)提高加鎖性能。為了提高吞吐率,在等待排隊的鎖中,排在最前面的如果是共享鎖,會一次把隊列中所有的共享鎖都加上,釋放之后就處理排他鎖。而不是當前是共享鎖,后面一來共享鎖請求都加上去,后面的請求是要排隊的,避免寫鎖的饑餓。
鎖的粒度
mongodb普遍使用全局鎖、數據庫鎖。還允許自己使用不同存儲引擎,實現更細粒度的鎖控制。比如MMAPv1存儲引擎支持表鎖,WiredTiger存儲引擎,支持文檔級別的鎖。
在 2.2 版本以前,mongodb只有全局鎖;也就是對整個mongodb實例加鎖,這個粒度是很大的。
從 2.2 版本開始,大部分讀寫操作只鎖一個庫,庫級別鎖相對之前版本,這個粒度已經下降。對于一些涉及到多個數據庫操作,還需要加全局鎖。目前MongoDB常見的版本是2.6、2.7,鎖粒度只到collection。
從 3.0 開始,如果使用MMAPv1存儲引擎,最細粒度支持 collection級別的鎖 ,對于一個數據庫里面的collection,對一個collection的加鎖操作,不影響其他collection的操作。
查看鎖的狀態
產生加鎖的操作操作鎖類型query讀鎖
cursor get more讀鎖
insert寫鎖
remove寫鎖
update寫鎖
Map-reduce讀鎖和寫鎖,除非Map-reduce操作被聲明為非原子性的。部分map-reduce任務能夠并發的執行。
createIndex創建索引操作默認在前臺執行,會長時間鎖住數據庫。
db.eval()寫鎖 db.eval()會加全局寫鎖,會阻塞其他的讀寫操作。加上參數nolock: true,表示不加鎖。
eval寫鎖. 同db.eval()
aggregate()讀鎖
###讀和寫操作讓出鎖
在需要長時間讀和非原子寫的操作,在一些條件下會讓出所占的鎖。比如update更新多個文檔時候,就有可能在中間讓出鎖。Mongodb使用一種自適應的算法基于預測磁盤訪問(比如缺頁中斷)方式決定是否讓出鎖。Monodb在讀之前預測所需要的數據是否在內存中,如果預測不在內存中則讓出鎖,等數據加載后,重新請求上鎖來完成操作。在2.6版本以后,對于索引,就算不在內存中,也不讓出鎖。
管理命令數據庫鎖
在運行下面的管理命令,需要長時間占鎖進行操作:db.collection.createIndex(), 如果沒有設置background:true參數,長時間鎖住數據庫。
reIndex;
compact;
db.repairDatabase(),
db.createCollection(), 比如要創建很大的固定空間capped collection時候;
db.collection.validate(), 返回磁盤信息;
db.copyDatabase(). 會鎖住多個數據庫。
對于上面的操作如果有副本集,建議將其中的一個副本下線后進行操作,這樣就不會影響上線服務。
下面的管理命令只占短時間鎖數據庫:db.collection.dropIndex(),
db.getLastError(),
db.isMaster(),
rs.status(),
db.serverStatus(),
db.auth(),
db.addUser()
鎖多個數據庫的全局鎖db.copyDatabase() 全局鎖,鎖整個monogdb的實例,不允許讀寫。
db.repairDatabase() 全局寫鎖。
Journaling,如果開啟了Journaling功能,在記錄日志時會很短時間鎖住所有的數據庫。所有的數據庫共享一個journaling。
對副本集主節點的所有寫操作不僅會鎖住目標庫,也會鎖住local庫很小一段時間。通過Local庫上的鎖, mongod進程可以往主節點的oplog 表寫數據,以及一些認證操作,不過這些都只占整個寫操作時間的很少一部分。
分片上鎖和并發
分片通過在多個mongod實例部署分布式集合來提高并發的性能,允許分片服務器(比如mongos進程)訪問多個mongd實例執行任意數量的并發操作。
mongod實例之間是相互獨立的,并且使用的是MongoDB多讀單寫鎖。mongod實例上的操作不會阻塞其它實例。
副本集主節點上的鎖和并發
在副本集中,當往主節點上某個表寫入時,MongoDB也會對主節點的oplog進行寫入,這是local庫上一個特殊的表。因此,MongoDB會鎖住目標表的庫和local庫。mongod進程會鎖住這兩個庫來保證數據一致性,讓這兩個庫的寫操作要么全執行要么全不執行。
副本集次級節點上的鎖和并發
在副本集中,次級節點會分批的收集oplog信息,這些批次的回寫是并行執行的。當在進行寫操作時,次級節點上的數據是不可讀的,回寫操作會按照oplog記錄的順序執行。
MongoDB允許副本集次級節點上的幾個寫操作并行執行,分兩個階段:
第一個階段,通過一個讀鎖,mongod確保所有與寫操作有關數據都存在于內存中。在這個階段,其它客戶端可以在這個節點上做查詢操作。
第二階段Mongodb會使用一個線程池使用寫鎖允許所有的寫操作相互協調的成批寫入。
MongoDB并發控制方法
Mongodb不支持事務,這是Mongodb的缺陷,要達到業務方面實現并發,保證數據的一致性,要使用一些并發的控制方法。
原子操作
mongodb對單個文檔的寫都是原子操作,就算修改一個文檔里面的多個內嵌文檔也算修改一個文檔,也是原子操作。
當一次寫操作中要修改多個文檔,每個文檔的寫操作是原子的,但整個操作不是原子的,期間會讓出鎖,允許其他線程操作。
使用update和findAndModify方法,如果都只對一個文檔進行操作,那么都是原子的。區別是返回的內容不一樣,findAndModify還可以返回當前改完之后的文檔,這個在很多場景很有用,比如實現一個自動增長的id。如果用update,然后findOne就不能保證返回的結果是剛剛更新后的記錄,可能別的線程又修改了。
$isolated
Mongodb中有個$isolated操作符,可以隔離其他的線程。 使用$isolate操作符時,可以阻止一次寫多個文檔中讓出鎖,直到操作完成或者出錯。
但是$isolate也無法保證多個文檔修改的原子性(all-or-nothing),$isolate操作如果在寫過程中出錯,是不會回滾的,也就是存在只修改部分文檔的情況。可以這么說$isolated操作符只提供隔離性,但不保證原子性。
操作符$isolated不能在分片上工作。
下面是使用$isolated操作符進行update的例子:db.test.update(
{ age : {$gt:30} , $isolated : 1 },
{ $inc : { score : 1 } },
{ multi: true }
)
當執行此操作時,其他線程就阻塞了,直到把所有操作更新完。這個操作會鎖數據庫比較長的時間,影響并發的效率,注意進行權衡。
使用唯一性索引
當你想要創建某個字段唯一的文檔時候,在多線程環境下,使用先查詢為空再插入的方法,仍會導致相同的字段記錄產生。解決的方法是在這個字段上建一個唯一的索引,在插入相同的字段時,會raise duplicate index key error。db.members.createIndex( { "name": 1 }, { unique: true } )
上面創建了一個name字段的唯一索引。
然后使用操作insert或者update方法增加和修改文檔,捕捉raise的duplicate index key錯誤,判斷是否操作成功。比如使用update操作:db.people.update(
{ name: "cf" },
{
name: "cf",
age: 18,
score: 100,
},
{ upsert: true }
)
用upsert參數表明如果不存在則進行插入操作,這樣做能保證name字段的值是唯一的,即便在多線程環境中。
事務
如果文檔的內容有兩個field,A和B,現在想根據B的值進行修改A的值,比如(A=B+N),也就是需要知道field B的內容,才能進行A修改。這時候使用update和findAndiModify就做不到了。
有兩種方法。
一種是兩階段提交事務語義(two-phase commit)方法,另外一種是update if current。
update if current
update if current是用手動的方式保證原子性,類似于CPU中常見的同步原語:比較和交換cas(Compare & Swap)操作,原理如下:
1、獲取需要更新的記錄,并記錄A字段的值,oldvalue;
2、本地修改好要更新的字段A;
3、使用update操作修改A字段值,但是查詢條件必須是A字段的值==oldvalue。
下面舉例說明:var myDocument = db.test.findOne( { name: 'cf' } );
if (myDocument) {
var oldValue = myDocument.score;
var age = myDocument.age
if (myDocument.score < 10) {
myDocument.score *= 5;
} else {
myDocument.score = age * 1.5;
}
var results = db.test.update(
{
name: myDocument.name,
score: oldValue
},
{
$set: { score: myDocument.score }
}
);
if ( results.hasWriteError() ) {
print("unexpected error updating document: " + tojson( results ));
} else if ( results.nMatched == 0 ) {
print("No update: no matching document for { name: " + myDocument.name + ", score: " + oldValue + " }")
}
上面的例子,score的值修改,跟score本身、age值相關,這就需要先獲取score、age的值,再進行修改。
你可以發現,這需要自己手動去檢查有沒有修改成功,如果不成功還需自己去控制再次嘗試更新。
兩階段提交事務
第一個階段嘗試進行提交,第二個階段正式提交。這樣,即使更新數據時發生故障,我們也能知道數據都處于什么狀態,總是能夠把數據恢復到更新之前的狀態。原理是使用一個表來記錄事務transaction,保存可能的狀態inital、pending、applied、done、canceling、canceled;要進行更新的collection的記錄中增加一個類型為列表pendingTransactions字段,用來綁定正在執行的transaction id。通過修改transaction里面記錄的狀態,確保事務執行的階段,這樣在發生故障時就知道transaction處于哪個階段,從而resume或者rollback。
具體例子可以看文檔:Two Phase Commits
兩階段提交方法達到事務一致性,我個人覺的還是比較麻煩,這也是mongodb的缺陷所在。
總結
以上是生活随笔為你收集整理的mongodb mysql并发_MongoDB:锁和并发控制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL入门之索引
- 下一篇: 动态调用object php,PHP动态