一、OCC和MVCC的區(qū)別
最簡(jiǎn)單的并發(fā)控制算法是2PL(2 Phase Locking),分為兩階段:
1)獲得鎖階段;
2)釋放鎖階段。
一般2PL被稱為是悲觀并發(fā)控制。
與之相對(duì)的是樂(lè)觀并發(fā)控制OCC( Optimistic Concurrency Control)。OCC假設(shè)事務(wù)會(huì)成功,開(kāi)始事務(wù)時(shí)該讀讀,該寫寫,不加鎖。只有到提交時(shí)做一下驗(yàn)證,驗(yàn)證這個(gè)事務(wù)是不是能夠成功提交。 OCC分為三階段:
1)Read Phase, 對(duì)于讀,放到Read Set,對(duì)于寫,把寫記到臨時(shí)副本,放到Write Set。因?yàn)閷懯菍懙脚R時(shí)區(qū)的,屬于未提交結(jié)果,其它事務(wù)讀不到(這點(diǎn)是和MVCC的重要區(qū)別);
2)Validation Phase,重掃Read Set,Write Set,檢驗(yàn)數(shù)據(jù)是否滿足Isolation Level,如果滿足則Commit,否則Abort;
3)WritePhase,或者叫做Commit Phase,把臨時(shí)副本區(qū)的數(shù)據(jù)更新到數(shù)據(jù)庫(kù)中,完成事務(wù)提交。
MVCC(Multiversion Concurrency Control)是另一種并發(fā)控制算法。MVCC為每條記錄維護(hù)多個(gè)快照(Snapshot),通過(guò)起止兩個(gè)時(shí)間戳(Begin Timestamp / End Timestamp)維護(hù)副本的可見(jiàn)性。讀寫進(jìn)行的不同操作如下:
Update,創(chuàng)建一個(gè)新的版本(Version);
Delete,更新End Timestamp。
Read,通過(guò)起止時(shí)間戳判定記錄是否對(duì)當(dāng)前事務(wù)可見(jiàn)(OCC讀不到未提交的記錄,所以不需要做這個(gè)判斷)。
這樣,通過(guò)Snapshot,實(shí)現(xiàn)了讀寫互不阻塞。但為了實(shí)現(xiàn)Serializable,對(duì)讀寫規(guī)則還是要進(jìn)行一定的限制。MVCC通過(guò)不同的方法實(shí)現(xiàn)。有基于鎖定的,MV-2PL,如MySQL。有基于時(shí)間排序(Time Ordering)的,叫MV-TO,如PostgreSQL。其實(shí)準(zhǔn)確來(lái)說(shuō),PG的實(shí)現(xiàn)叫SSI(Serializable Snapshot Isolation),不算MV-TO。也有像OCC那樣基于樂(lè)觀算法的,MV-OCC,即讀寫時(shí)不做驗(yàn)證,延遲到提交時(shí)驗(yàn)證。
效率上,2PL讀寫阻塞,在維護(hù)鎖開(kāi)銷較小時(shí)較好;OCC不維護(hù)鎖,一些比較新的OCC算法吞吐可以做得很高,不過(guò)相應(yīng)回滾也會(huì)比較高,沖突比較小和驗(yàn)證開(kāi)銷小時(shí)比較好;MVCC對(duì)不同類型的workload都有很好的適應(yīng)性,讀寫互不阻塞,回滾率也比OCC好,很多RDBMS也都用MVCC,如Oracle,PostgreSQL,MySQL。還有一個(gè)效率問(wèn)題,隨著現(xiàn)在CPU核心數(shù)越來(lái)越多,考慮并發(fā)控制算法往往需要考慮它的多核擴(kuò)展性好不好。由于多數(shù)MVCC,OCC算法都需要時(shí)間戳分配,時(shí)間戳通常對(duì)全局變量進(jìn)行CAS(Compare And Swap)操作來(lái)計(jì)算,當(dāng)核心數(shù)變大時(shí),CAS的爭(zhēng)用也變大了。
另外,現(xiàn)在的許多并發(fā)控制方法經(jīng)常混合了多種算法。先有人提出了A,后有人提出了B,再后來(lái)就有人提出了A+B,那么A+B應(yīng)該是叫A呢還是叫B呢?就像上面提到的MV-2PL,MV-TO,MV-OCC。
延伸閱讀:
二、id的一些典型的類型
整型:整型通常來(lái)說(shuō)是優(yōu)異的選擇,這是因?yàn)檎偷倪\(yùn)算和比較都很快,而且還可以設(shè)置 AUTO_INCREMENT 屬性自動(dòng)遞增。ENUM 和 SET:通常不會(huì)選擇枚舉和集合作為 id,然后對(duì)于那些包含有“類型”、“狀態(tài)”、“性別”這類型的列來(lái)說(shuō)是挺合適的。例如我們需要有一張表存儲(chǔ)下拉菜單時(shí),通常會(huì)有一個(gè)值和一個(gè)名稱,這個(gè)時(shí)候值使用枚舉作為主鍵也是可以的。字符串:盡可能地避免使用字符串作為 id,一是字符串占據(jù)的空間更大,二是通常會(huì)比整型慢。選用字符串作為 id 時(shí),還需要特別注意 MD5、SHA1和 UUID 這些函數(shù)。每個(gè)值是在很大范圍的隨機(jī)值,沒(méi)有次序,這會(huì)導(dǎo)致插入和查詢更慢:插入的時(shí)候,由于建立索引是隨機(jī)位置(會(huì)導(dǎo)致分頁(yè)、隨機(jī)磁盤訪問(wèn)和聚集索引碎片),會(huì)降低插入速度。查詢的時(shí)候,相鄰的數(shù)據(jù)行在磁盤或內(nèi)存上上可能跨度很大,也會(huì)導(dǎo)致速度更慢。如果確實(shí)要使用 UUID 值,應(yīng)當(dāng)移除掉“-”字符,或者是使用 UNHEX 函數(shù)將其轉(zhuǎn)換為16字節(jié)數(shù)字,并使用 BINARY(16)存儲(chǔ)。然后可以使用 HEX 函數(shù)以十六進(jìn)制的方式進(jìn)行獲取。UUID 產(chǎn)生的方法有很多,有些是隨機(jī)分布的,有些是有序的,但是即便是有序的性能也不如整型。