(十四)并發(fā)控制

??之前在(十三)事務(wù)處理中簡(jiǎn)單的介紹了鎖系統(tǒng),并且介紹了由于并發(fā)而產(chǎn)生的種種問(wèn)題,例如臟讀、幻讀等,因此這里對(duì)如何解決這些問(wèn)題再進(jìn)行一下補(bǔ)充。


1、悲觀鎖

??在關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)里,悲觀并發(fā)控制是一種并發(fā)控制的方法,它可以阻止一個(gè)事務(wù)以影響其他事務(wù)的方式來(lái)修改數(shù)據(jù)。悲觀鎖需要使用數(shù)據(jù)庫(kù)的鎖機(jī)制,可以從字面理解為這種并發(fā)方式就是很悲觀,每次調(diào)用數(shù)據(jù)的時(shí)候都認(rèn)為同時(shí)會(huì)有其他事務(wù)在修改數(shù)據(jù),因此每次在調(diào)用數(shù)據(jù)前都會(huì)先上鎖,這樣可以防止其他事務(wù)讀取或修改表中的數(shù)據(jù)。

??例如:



??此處之所以會(huì)出現(xiàn)死鎖,就是因?yàn)閳?zhí)行這條語(yǔ)句時(shí)已經(jīng)使用了鎖,

UPDATE tbl_name SET col_name = newValue WHERE id = x;

??因此互相調(diào)用相同的字段造成了死鎖。

??悲觀并發(fā)控制主要用于數(shù)據(jù)爭(zhēng)用激烈的環(huán)境,以及發(fā)生并發(fā)沖突時(shí)使用鎖保護(hù)數(shù)據(jù)的成本要低于回滾事務(wù)的成本的環(huán)境中。悲觀并發(fā)控制實(shí)際上是”先取鎖再訪問(wèn)”的保守策略,為數(shù)據(jù)處理的安全提供了保證。但是在效率方面,處理加鎖的機(jī)制會(huì)讓數(shù)據(jù)庫(kù)產(chǎn)生額外的開銷,并且會(huì)增加產(chǎn)生死鎖的機(jī)會(huì);另外,在只讀型事務(wù)處理中由于不會(huì)產(chǎn)生沖突,也沒(méi)必要使用鎖,這樣做只能增加系統(tǒng)負(fù)載,還會(huì)降低并行性,因?yàn)橐粋€(gè)事務(wù)如果鎖定了某行數(shù)據(jù),其他事務(wù)就必須等待該事務(wù)處理完才可以進(jìn)行處理。


2、樂(lè)觀鎖

??樂(lè)觀鎖相對(duì)于悲觀鎖而言,通常會(huì)假設(shè)多用戶并發(fā)的事務(wù)在處理時(shí)不會(huì)彼此互相影響,各事務(wù)能夠在不產(chǎn)生鎖的情況下處理各自負(fù)責(zé)的數(shù)據(jù)。在提交數(shù)據(jù)更新之前,每個(gè)事務(wù)會(huì)先檢查在讀取數(shù)據(jù)后,有無(wú)其他事務(wù)再次修改了該數(shù)據(jù)。如果有,那么當(dāng)前正在提交的事務(wù)會(huì)進(jìn)行回滾??梢詮淖置胬斫鉃檫@種并發(fā)方式就是很樂(lè)觀,每次調(diào)用數(shù)據(jù)的時(shí)候都認(rèn)為其他事務(wù)不會(huì)在同時(shí)修改數(shù)據(jù),因此不會(huì)上鎖。

??在處理數(shù)據(jù)時(shí),樂(lè)觀鎖并不會(huì)使用數(shù)據(jù)庫(kù)提供的鎖機(jī)制,通常樂(lè)觀鎖的實(shí)現(xiàn)方式是記錄數(shù)據(jù)版本,即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),一般是通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè)數(shù)字類型的 “version” 字段來(lái)實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)時(shí),將version字段的值一同讀出,數(shù)據(jù)每更新一次,對(duì)此version值加1。當(dāng)提交更新的時(shí)候,判斷當(dāng)前version值是否與之前讀出的version值一致,如果相同,則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。

??例如:
??假設(shè)用戶A和用戶B對(duì)同一張數(shù)據(jù)表的同一個(gè)字段記錄值進(jìn)行修改,此時(shí)用戶A和用戶B從該表中讀取的version值為2,用戶A對(duì)記錄修改結(jié)束后,version值增加1,此時(shí)該表的version字段的值是3,而用戶B依然按照version值為2進(jìn)行操作,不滿足“提交版本必須大于記錄當(dāng)前版本才能執(zhí)行更新”的樂(lè)觀鎖策略,因此,用戶B的提交被駁回。這樣,就避免了用戶B用基于version值為2的舊數(shù)據(jù)修改的結(jié)果覆蓋用戶A的操作結(jié)果的可能。

??樂(lè)觀并發(fā)控制多數(shù)用于數(shù)據(jù)爭(zhēng)用不大、沖突較少的環(huán)境中,此時(shí)偶爾回滾事務(wù)的成本會(huì)低于讀取數(shù)據(jù)時(shí)鎖定數(shù)據(jù)的成本,因此可以獲得比其他并發(fā)控制方法更高的吞吐量。


3、MVCC

??多版本并發(fā)控制(Multi-Version Concurrency Control)是為了實(shí)現(xiàn)數(shù)據(jù)庫(kù)的并發(fā)控制而設(shè)計(jì)的一種機(jī)制。大多數(shù)的關(guān)系型數(shù)據(jù)庫(kù)都支持MVCC,其突出特點(diǎn)是:讀不加鎖,讀寫不沖突。

??在MVCC中,讀操作可以分成兩類,快照讀和當(dāng)前讀:

  • 快照讀,讀取的是記錄的可見版本(可能是歷史版本,即最新的數(shù)據(jù)可能正在被當(dāng)前執(zhí)行的事務(wù)并發(fā)修改),不會(huì)對(duì)返回的記錄加鎖;
  • 當(dāng)前讀,讀取的是記錄的最新版本,并且會(huì)對(duì)返回的記錄加鎖,保證其他事務(wù)不會(huì)并發(fā)修改這條記錄。

??在MySQL InnoDB中,基本的SELECT操作,如

SELECT * FROM tbl_name WHERE xxxx;

??都屬于快照讀;而屬于當(dāng)前讀的包含以下操作:

SELECT * FROM tbl_name WHERE xxxx LOCK IN SHARE MODE;(共享鎖)
SELECT * FROM tbl_name WHERE xxxx FOR UPDATE;(排他鎖)
INSERT,UPDATE,DELETE操作(排他鎖)

??可以將MVCC理解為行級(jí)鎖的一種妥協(xié),它在許多情況下避免了使用鎖,同時(shí)可以提供更小的開銷。根據(jù)實(shí)現(xiàn)的不同,它可以允許非阻塞式讀,在寫操作進(jìn)行時(shí)只鎖定必要的記錄。

??各個(gè)存儲(chǔ)引擎對(duì)于MVCC的實(shí)現(xiàn)各不相同,下面將通過(guò)一個(gè)簡(jiǎn)化的InnoDB版本的行為來(lái)展示MVCC工作原理:
簡(jiǎn)單來(lái)說(shuō),通過(guò)為每一行記錄添加兩個(gè)額外的隱藏的值來(lái)實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過(guò)期(或者被刪除)。但是InnoDB并不存儲(chǔ)這些事件發(fā)生時(shí)的實(shí)際時(shí)間,相反它只存儲(chǔ)這些事件發(fā)生時(shí)的系統(tǒng)版本號(hào)。這是一個(gè)隨著事務(wù)的創(chuàng)建而不斷增長(zhǎng)的數(shù)字。每個(gè)事務(wù)在事務(wù)開始時(shí)會(huì)記錄它自己的系統(tǒng)版本號(hào)。每個(gè)查詢必須去檢查每行數(shù)據(jù)的版本號(hào)與事務(wù)的版本號(hào)是否相同。

??以下是在默認(rèn)隔離級(jí)別REPEATABLE READ下,MVCC具體是怎樣實(shí)現(xiàn)的:

  • SELECT:
    ??InnoDB只查找版本早于(包含等于)當(dāng)前事務(wù)版本的數(shù)據(jù)行。這保證了不管是事務(wù)開始之前,或者事務(wù)創(chuàng)建時(shí),或者修改了這行數(shù)據(jù)的時(shí)候,這行數(shù)據(jù)是存在的;這行數(shù)據(jù)的刪除版本必須是未定義的或者比事務(wù)版本要大,這可以保證在事務(wù)開始之前這行數(shù)據(jù)沒(méi)有被刪除。
    ??符合這兩個(gè)條件的行可能會(huì)被當(dāng)作查詢結(jié)果而返回。

  • INSERT:
    InnoDB為這個(gè)新行記錄當(dāng)前的系統(tǒng)版本號(hào)。

  • DELETE:
    InnoDB將當(dāng)前的系統(tǒng)版本號(hào)設(shè)置為這一行的刪除ID。

  • UPDATE:
    InnoDB會(huì)寫一個(gè)這行數(shù)據(jù)的新拷貝,這個(gè)拷貝的版本為當(dāng)前的系統(tǒng)版本號(hào)。它同時(shí)也會(huì)將這個(gè)版本號(hào)寫到舊行的刪除版本里。


4、MVCC與樂(lè)觀鎖的區(qū)別

??在了解了MVCC的實(shí)現(xiàn)機(jī)制后可能會(huì)感覺與樂(lè)觀鎖中使用版本號(hào)加鎖有相似之處,實(shí)際上MVCC可以保證不阻塞地讀到一致的數(shù)據(jù)。但是,MVCC并沒(méi)有對(duì)實(shí)現(xiàn)細(xì)節(jié)做約束,在InnoDB引擎下是只對(duì)讀無(wú)鎖,寫操作仍是上鎖的悲觀并發(fā)控制,這也意味著,InnoDB中只能見到因死鎖和不變性約束而回滾,而不會(huì)出現(xiàn)因?yàn)閷憶_突而回滾的現(xiàn)象;MVCC對(duì)數(shù)據(jù)表中的每行數(shù)據(jù)只保留一份,在更新數(shù)據(jù)時(shí)上行級(jí)鎖,同時(shí)將舊版數(shù)據(jù)寫入undo log;數(shù)據(jù)表和undo log中行數(shù)據(jù)都記錄著事務(wù)ID,在檢索時(shí),只讀取來(lái)自當(dāng)前已提交的事務(wù)的行數(shù)據(jù)。這種額外的記錄所帶來(lái)的結(jié)果就是對(duì)于大多數(shù)查詢來(lái)說(shuō)根本就不需要獲得一個(gè)鎖。MVCC只是簡(jiǎn)單地以最快的速度來(lái)讀取數(shù)據(jù),確保只選擇符合條件的行。但其缺點(diǎn)也正是存儲(chǔ)引擎必須為每一行存儲(chǔ)更多的數(shù)據(jù),做更多的檢查工作,處理更多的善后操作。


版權(quán)聲明:歡迎轉(zhuǎn)載,歡迎擴(kuò)散,但轉(zhuǎn)載時(shí)請(qǐng)標(biāo)明作者以及原文出處,謝謝合作!             ↓↓↓
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容