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