數(shù)據(jù)庫的樂觀鎖和悲觀鎖并非真實(shí)的鎖

開局

我們平時(shí)編寫程序的時(shí)候,有很多情況下需要考慮線程安全問題,一個(gè)全局的變量如果有可能會(huì)被多個(gè)同時(shí)執(zhí)行的線程去修改,那么對(duì)于這個(gè)變量的修改就需要有一種機(jī)制去保證值的正確性和一致性,這種機(jī)制普遍的做法就是加鎖。其實(shí)也很好理解,和現(xiàn)實(shí)中一樣,多個(gè)人同時(shí)修改一個(gè)東西,必須有一種機(jī)制來把多個(gè)人進(jìn)行排隊(duì)。計(jì)算機(jī)的世界中也是如此,多個(gè)線程乃至多個(gè)進(jìn)程同時(shí)修改一個(gè)變量,必須要對(duì)這些線程或者進(jìn)程進(jìn)行排隊(duì)。數(shù)據(jù)庫的世界亦是如此,多個(gè)請(qǐng)求同時(shí)修改同一條數(shù)據(jù)記錄,數(shù)據(jù)庫必須需要一種機(jī)制去把多個(gè)請(qǐng)求來順序化,或者理解為同一條數(shù)據(jù)記錄同一時(shí)間只能被一個(gè)請(qǐng)求修改。

鎖是數(shù)據(jù)庫中最為重要的機(jī)制之一,無論平時(shí)寫的select語句,還是update語句其實(shí)在數(shù)據(jù)庫層面都和鎖息息相關(guān)。如果沒有鎖機(jī)制,操作數(shù)據(jù)的時(shí)候可能會(huì)發(fā)生以下情況:

  1. 更新丟失:多個(gè)用戶同時(shí)對(duì)一個(gè)數(shù)據(jù)資源進(jìn)行更新,必定會(huì)產(chǎn)生被覆蓋的數(shù)據(jù),造成數(shù)據(jù)讀寫異常。
  2. 不可重復(fù)讀:如果一個(gè)用戶在一個(gè)事務(wù)中多次讀取一條數(shù)據(jù),而另外一個(gè)用戶則同時(shí)更新啦這條數(shù)據(jù),造成第一個(gè)用戶多次讀取數(shù)據(jù)不一致。
  3. 臟讀:第一個(gè)事務(wù)讀取第二個(gè)事務(wù)正在更新的數(shù)據(jù)表,如果第二個(gè)事務(wù)還沒有更新完成,那么第一個(gè)事務(wù)讀取的數(shù)據(jù)將是一半為更新過的,一半還沒更新過的數(shù)據(jù),這樣的數(shù)據(jù)毫無意義。
  4. 幻讀:第一個(gè)事務(wù)讀取一個(gè)結(jié)果集后,第二個(gè)事務(wù),對(duì)這個(gè)結(jié)果集經(jīng)行增刪操作,然而第一個(gè)事務(wù)中再次對(duì)這個(gè)結(jié)果集進(jìn)行查詢時(shí),數(shù)據(jù)發(fā)現(xiàn)丟失或新增。

數(shù)據(jù)管理角度

在數(shù)據(jù)庫管理的角度或者數(shù)據(jù)行的角度來說,數(shù)據(jù)庫鎖可以分為共享鎖和排它鎖,這是面試過程中經(jīng)常被提及的兩種類型。本質(zhì)其實(shí)很簡(jiǎn)單,站在數(shù)據(jù)的角度來看,如果數(shù)據(jù)當(dāng)前正在被訪問,下一個(gè)訪問的請(qǐng)求該如何處理?和計(jì)算機(jī)二進(jìn)制一樣,無非就是允許被訪問和不允許訪問兩種狀態(tài)。

共享鎖

共享所被稱為讀鎖或者S鎖,就像以上所述,共享鎖在新請(qǐng)求訪問一個(gè)數(shù)據(jù)的時(shí)候,如果是讀請(qǐng)求則允許,如果是寫(刪改)請(qǐng)求,則不允許。由于共享鎖允許其他的讀操作,所以通常情況下共享鎖只應(yīng)用于select操作,如果一個(gè)update或者delete操作應(yīng)用共享鎖會(huì)發(fā)生很嚴(yán)重的數(shù)據(jù)不一致情況。

獨(dú)占鎖

獨(dú)占鎖也被稱為排它鎖或者X鎖,相對(duì)于共享鎖,獨(dú)占鎖采用的態(tài)度比較堅(jiān)決,一旦數(shù)據(jù)被獨(dú)占鎖鎖定,其他任何請(qǐng)求(包括讀操作)都必須等待獨(dú)占鎖的釋放才可以繼續(xù),只有當(dāng)前鎖定數(shù)據(jù)的請(qǐng)求才可以修改讀取數(shù)據(jù)。

更新鎖

當(dāng)數(shù)據(jù)庫準(zhǔn)備更新數(shù)據(jù)時(shí),它首先對(duì)數(shù)據(jù)對(duì)象作更新鎖鎖定,這樣數(shù)據(jù)將不能被修改,但可以讀取。等到確定要進(jìn)行更新數(shù)據(jù)操作時(shí),他會(huì)自動(dòng)將更新鎖換為獨(dú)占鎖,當(dāng)對(duì)象上有其他鎖存在時(shí),無法對(duì)其加更新鎖。

意向鎖

簡(jiǎn)單來說就是給更大一級(jí)別的空間示意里面是否已經(jīng)上過鎖。例如表級(jí)放置了意向鎖,就表示事務(wù)要對(duì)表的頁或行上使用共享鎖。在表的某一行上上放置意向鎖,可以防止其它事務(wù)獲取其它不兼容的的鎖。意向鎖可以提高性能,因?yàn)閿?shù)據(jù)引擎不需要檢測(cè)資源的每一列每一行,就能判斷是否可以獲取到該資源的兼容鎖。意向鎖包括三種類型:意向共享鎖(IS),意向排他鎖(IX),意向排他共享鎖(SIX)。

實(shí)際應(yīng)用中,站在數(shù)據(jù)的角度可以看出,數(shù)據(jù)只允許同時(shí)進(jìn)行一個(gè)寫操作

顆粒度角度

鎖用來對(duì)數(shù)據(jù)進(jìn)行鎖定,我們可以從鎖定對(duì)象的粒度大小來對(duì)鎖進(jìn)行劃分,分別為行鎖、頁鎖和表鎖。

  1. 行級(jí)鎖是數(shù)據(jù)庫中鎖定粒度最細(xì)的一種鎖,表示只針對(duì)當(dāng)前操作的行進(jìn)行加鎖。行級(jí)鎖能大大減少數(shù)據(jù)庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大。特點(diǎn):開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。
  2. 表級(jí)鎖是數(shù)據(jù)庫中鎖定粒度最大的一種鎖,表示對(duì)當(dāng)前操作的整張表加鎖,它實(shí)現(xiàn)簡(jiǎn)單,資源消耗較少。特點(diǎn):開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)出鎖沖突的概率最高,并發(fā)度最低。
  3. 頁級(jí)鎖是數(shù)據(jù)庫中鎖定粒度介于行級(jí)鎖和表級(jí)鎖中間的一種鎖。表級(jí)鎖速度快,但沖突多,行級(jí)沖突少,但速度慢。所以取了折衷的頁級(jí),一次鎖定相鄰的一組記錄。特點(diǎn):開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般

不同數(shù)據(jù)庫支持的鎖力度不同,甚至同一種數(shù)據(jù)庫不同的引擎支持的鎖力度都不同,如下表所示(來源于網(wǎng)絡(luò))


image

這里要強(qiáng)調(diào)一點(diǎn),無論什么數(shù)據(jù)庫對(duì)數(shù)據(jù)加鎖,都需要資源的消耗,因此鎖的數(shù)量其實(shí)是有上限的,當(dāng)鎖數(shù)量到達(dá)這個(gè)上限會(huì)自動(dòng)進(jìn)行鎖力度的升級(jí),用更大力度的鎖來代替多個(gè)小力度的鎖。

樂觀鎖和悲觀鎖

樂觀鎖

樂觀鎖認(rèn)為一般情況下數(shù)據(jù)不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新時(shí)才會(huì)對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè)。如果沒有沖突那就OK;如果出現(xiàn)沖突了,則返回錯(cuò)誤信息并讓用戶決定如何去做。類似于 SVN、GIt這些版本管理系統(tǒng),當(dāng)修改了某個(gè)文件需要提交的時(shí)候,它會(huì)檢查文件的當(dāng)前版本是否與服務(wù)器上的一致,如果一致那就可以直接提交,如果不一致,那就必須先更新服務(wù)器上的最新代碼然后再提交(也就是先將這個(gè)文件的版本更新成和服務(wù)器一樣的版本)

樂觀鎖是一種程序的設(shè)計(jì)思想,通過一個(gè)標(biāo)識(shí)的對(duì)比來決定數(shù)據(jù)是否可以操作,現(xiàn)在普遍的做法是給數(shù)據(jù)加一個(gè)版本號(hào)或者時(shí)間戳的方式來實(shí)現(xiàn)樂觀鎖操作過程:
在表中設(shè)計(jì)一個(gè)版本字段 version,第一次讀的時(shí)候,會(huì)獲取 version 字段的取值。然后對(duì)數(shù)據(jù)進(jìn)行更新或刪除操作時(shí),會(huì)執(zhí)行UPDATE ... SET version=version+1 WHERE version=version。此時(shí)如果已經(jīng)有事務(wù)對(duì)這條數(shù)據(jù)進(jìn)行了更改,修改就不會(huì)成功。

悲觀鎖

每次獲取數(shù)據(jù)的時(shí)候,都會(huì)擔(dān)心數(shù)據(jù)被修改,所以每次獲取數(shù)據(jù)的時(shí)候都會(huì)進(jìn)行加鎖,確保在自己使用的過程中數(shù)據(jù)不會(huì)被別人修改,使用完成后進(jìn)行數(shù)據(jù)解鎖。由于數(shù)據(jù)進(jìn)行加鎖,期間對(duì)該數(shù)據(jù)進(jìn)行讀寫的其他線程都會(huì)進(jìn)行等待。

總結(jié)

無論是樂觀鎖和悲觀鎖,并非是數(shù)據(jù)庫自身持有的鎖類型(雖然悲觀鎖形式上很像獨(dú)占鎖),而是程序設(shè)計(jì)的一種思想,是一種類似數(shù)據(jù)庫鎖機(jī)制保護(hù)數(shù)據(jù)一致性的策略。

  1. 悲觀鎖比較適合寫入操作比較頻繁的場(chǎng)景,如果出現(xiàn)大量的讀取操作,每次讀取的時(shí)候都會(huì)進(jìn)行加鎖,這樣會(huì)增加大量的鎖的開銷,降低了系統(tǒng)的吞吐量。
  2. 樂觀鎖比較適合讀取操作比較頻繁的場(chǎng)景,如果出現(xiàn)大量的寫入操作,數(shù)據(jù)發(fā)生沖突的可能性就會(huì)增大,為了保證數(shù)據(jù)的一致性,應(yīng)用層需要不斷的重新獲取數(shù)據(jù),這樣會(huì)增加大量的查詢操作,降低了系統(tǒng)的吞吐量。

寫在最后

程序編寫過程中,操作數(shù)據(jù)無論采用哪個(gè)類型的鎖,都需要注意死鎖的發(fā)生,一個(gè)死鎖有可能對(duì)整個(gè)應(yīng)用是致命的。死鎖的本質(zhì)是對(duì)資源競(jìng)爭(zhēng)的一種失敗表現(xiàn),所以sql語句的編寫過程中對(duì)于多表的操作最好采用一致的順序來進(jìn)行,另外一個(gè)種極端的方式可以一次性鎖定所有資源,而非逐步來鎖資源。

更多精彩文章

?著作權(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)容