1. 程式人生 > >對資料庫事務、隔離級別、鎖、封鎖協議的理解及其關係的理解

對資料庫事務、隔離級別、鎖、封鎖協議的理解及其關係的理解

前言

寫篇文章來鞏固自己學習的知識。通過寫文章,本著一顆不誤導別人、不混過去的心,找出那些自己看似懂了的問題。這篇文章主要講的是事務、隔離級別、鎖、封鎖協議之間的關聯。我覺得最重要的是要將這些知識關聯起來。而不是單獨的去理解。
在這裡提一個問題。在瞭解一級封鎖協議的定義後,你能解釋為什麼一級封鎖協議能解決更新丟失的問題嗎。如果不能,那麼這篇文章也算是能給你帶來一點收穫了。

事務的四大特性

1.原子性
2.一致性
3.隔離性
4.永續性

事務在併發下產生的問題

併發情況下,事務會出現什麼問題呢?

1.更新丟失
2.髒讀
3.不可重複讀
4.幻讀

【事務】和【併發】的關係

:單執行緒下事務不會出問題,因為是單個執行緒在操作資料庫,事務操作成功則記錄到磁碟中。失敗則進行回滾。但是併發情況下,有多個事務在操作資料庫,這樣,就有可能產生一些問題。

舉例來說。
更新丟失:a事務中更新一條記錄,卻被b事務的更新操作給覆蓋了。也就是說,a事務的操作沒有生效。
髒讀:b事務中執行了修改操作:將name從lee改成了tom,但是還沒提交,a事務中讀到到的資料是tom,當事務b回滾,name變回了lee。這樣就導致了a事務中讀到的tom是不存在的。
不可重複讀:事務a中讀取了name欄位,其值為lee。在第二次讀取name欄位的過程中,事務b將name欄位的值改成了jack,事務a第二次讀到的是jack,發現與第一次讀到的不一樣,即兩次讀到的資料不一樣
幻讀:假設表(欄位有id主鍵和value)中有三條記錄,分別為(1,a),(2,b),(3,c)。a事務讀取全部發現這三條,b事務新增一條記錄(4,d),然後提交事務。然後a事務再查詢,發現還是這三條(沒能讀取到最新那條),於是新增一條(4,d)。結果報錯了,說主鍵重複了。可是事務a中查詢到的記錄明明沒有id為4的記錄。這就是幻讀。

事務的四個隔離級別

mysql的隔離級別有四種:

序號 英文名 中文名 會產生的問題
1 read_uncommited 讀未提交(可讀未提交的) 會產生髒讀、不可重複讀、幻讀問題
2 read_commited 讀已提交(提交了才能讀取到) 會產生不可重複讀、幻讀問題
3 repeatable_read 可重複讀 會產生幻讀問題
4 serilizable 序列化讀 不會產生問題

上面的表述讓人很難理解,或者說是有歧義的。不應該說哪種隔離級別會產生哪種問題,而是應該說哪種隔離級別沒能解決哪種問題。

事務在併發情況下,會出現這些問題:丟失更新、髒讀、不可重複讀、幻讀。而資料庫的隔離級別,是用來解決多個事務操作同一資料庫物件時出現的衝突問題。

事務在併發下會出現問題,而使用不同的事務隔離級別,可以不同程度的解決這些問題。所以,應該這樣寫才對:

序號 英文名 中文名 沒能解決的問題
1 read_uncommited 讀未提交(可讀未提交的) 沒能解決事務併發操作的 髒讀、不可重複讀、幻讀問題
2 read_commited 讀已提交(提交了才能讀取到) 沒能解決事務併發操作的不可重複讀、幻讀問題
3 repeatable_read 可重複讀 沒能解決事務併發操作的幻讀問題
4 serilizable 序列化讀 能解決事務併發操作的所有問題

【隔離級別】與【併發下事務的問題】的關係:通過設定隔離級別,就相應的解決這些併發情況下帶來的問題。

資料庫中的鎖

1.共享鎖(又稱為讀鎖和S鎖)
2.排它鎖(又稱為寫鎖和X鎖)

排他鎖:若事務T對資料物件A加上X鎖,則只允許T讀取和修改A,其他任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖為止。
共享鎖:若事務T對資料物件A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖為止。

【事務隔離】與【鎖】的關係:事務隔離主要就是對事務的讀寫之間進行隔離,通過什麼來隔離?通過鎖來實現隔離

【事務隔離級別】與【鎖】的關係:通過對事務的讀寫操作加鎖情況的不同,劃分出不同的事務隔離級別

為什麼repeatable_read能解決更新丟失、髒讀、不可重複讀的問題,而read_commited只能解決更新丟失、髒讀的問題,而read_uncommited只能解決更新丟失的問題?

因為它們在事務讀寫操作上所用的鎖不同。要想明白,不同的隔離級別就是對事務的讀寫操作加鎖情況的不同

【鎖】與【事務在併發下的問題】的關係:通過對事務的讀寫操作加鎖,能解決事務在併發下的問題。

理解背後的原因

我覺得,要想看懂資料庫事務這一塊,最重要的是要搞明白上面說的幾個關係。而不能每個部分單獨的去理解。

前面說到的這個:

1.read_uncommited 讀未提交(可讀未提交的) 沒能解決事務併發操作的 髒讀、不可重複讀、幻讀問題
2.read_commited 讀已提交(提交了才能讀取到) 沒能解決事務併發操作的 不可重複讀、幻讀問題
3.repeatable_read 可重複讀 沒能解決事務併發操作的 幻讀問題
4.serilizable 序列化讀 能解決事務併發操作的所有問題

這只是一個結論,只是告訴我們不同的隔離級別能解決併發下事務的哪些問題,沒能解決哪些問題。

而接下來,我們要做的,是要去搞明白結論從何而來,去理解背後的原因。也就是去理解這些不同的隔離級別對事務的讀寫操作是怎麼加鎖的。去理解對事務的讀寫操作加鎖是如何解決併發下的問題的。

理解了背後的原因,也就能理解為什麼這種事務隔離級別無法解決那種併發問題,而那種事務隔離級別卻可以。

一級封鎖協議

一級封鎖協議是指,事務T在修改資料R之前必須先對其進行加X鎖,直到事務T結束才釋放。(事務結束包括正常結束COMMIT和非正常結束ROLLBACK)。

一級封鎖協議可以防止丟失更新的問題。事務T1想要對資料R進行修改,那麼先得加X鎖。這時事務T2也想對資料R進行修改,那得先給資料R上X鎖。但是由於資料R已經被上了X鎖,此時事務T2無法對其進行上X鎖,因此只能一直等待,直到資料R上的X鎖被釋放,才能給它上X鎖,才進行修改操作。如此,解決了丟失更新的問題。

書上說:“在一級協議中,如果僅僅是讀取資料而沒對其進行修改,是不需要加鎖的,所以它不能保證可重複讀和不讀髒資料”。乍一看覺得沒問題,但仔細想想感覺不太對,無法理解啊。這解釋我無法接受啊。我的理解是使用了一級封鎖協議,事務T1給資料R加了X鎖,直到事務T1結束才會釋放X鎖。這樣,事務T2不是隻能在事務T1結束之後才能對資料R進行上鎖,再進行相應操作嗎???這樣怎麼可能會發生髒讀和不可重複讀的問題呢。但是這樣一來,加X鎖不就相當於序列化讀了嗎??

查閱了資料(百度知道)。才明白是書上的表述有誤:
若事務T對資料物件A加上X鎖,則只允許T讀取和修改A,其他任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖為止。這就保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。

藍色字的表述是有誤的。

我的理解

若事務T對資料物件A加上X鎖,則其他事務在T釋放A上的鎖之前不能再對資料A上鎖。倘若,事務T2中沒有上任何鎖,那麼它是可以直接讀取和修改資料A的。因此,並不是說,事務T對資料A上X鎖到釋放X鎖期間,其他事務就不能訪問資料A。

但是,如果選擇了封鎖協議,情況就不一樣了。譬如選擇了使用一級封鎖協議,那麼根據一級封鎖協議規定,事務T在修改資料R之前必須先對其進行加X鎖,直到事務T結束才釋放。也就是說,如果一個事務中有修改資料的操作,那麼它必須先對該資料上X鎖,才能進行修改操作。

如此,在一級封鎖協議下,若事務T對資料物件A加上X鎖,則其他事務在T釋放A上的鎖之前不能再對資料A上鎖,因此也就沒有機會去修改資料A了。

但是,要注意的是,一級封鎖協議規定的是:“事務T在修改資料R之前必須先對其進行加X鎖,直到事務T結束才釋放。”它對讀取資料沒有作規定啊。因此,在一級封鎖協議下,如果一個事務只有讀取資料A的操作,那麼即使資料A被上了X鎖,那也沒影響,照樣可以讀取。

也正是因為一級封鎖協議下,讀取操作是不受限制的。因此,一級封鎖協議協議沒能解決髒讀和不可重複讀的問題。舉例來說,事務a執行了更新操作,將記錄為(1,‘a’)的記錄改成了(1,‘b’),這時事務b執行了查詢操作,查詢到這條記錄是(1,‘b’)。但是事務a回滾了,該記錄變回了(1,‘a’)。如此,事務b讀到的(1,‘b’)就是不存在的記錄,這就是髒讀。而不可重複讀呢,如果事務a執行了查詢操作,讀到的是(1,‘a’),然後這時事務b執行了更新操作,將記錄為(1,‘a’)的記錄改成了(1,‘b’),這時事務a進行驗算操作,再查詢一次,發現記錄變成了(1,‘b’)。也就是說,事務a兩次讀取的記錄是不一樣,這就是不可重複讀問題(重複讀取結果卻不一樣,這就是不可重複讀問題)。

可能我的表述還是沒那麼清晰,沒關係,繼續往下看,看了二級封鎖協議和三級封鎖協議就懂了。

二級封鎖協議

二級封鎖協議是指,在一級封鎖協議的基礎上增加事務T在讀取資料R之前必須先對其加S鎖,讀完後即可釋放S鎖。

二級封鎖協議是在一級封鎖協議的基礎上的,因此它必然能解決丟失更新的問題,除此之外還能解決髒讀問題。這要為什麼呢。這是因為二級封鎖協議規定,在事務T在讀取資料R之前必須先對其加S鎖。舉例來說明,還是上面髒讀的例子。事務a執行了更新操作,將記錄為(1,‘a’)的記錄改成了(1,‘b’),這時事務b想執行查詢id為1的這條記錄,二級封鎖協議規定了,讀取之前得先對資料進行加S鎖。然而此時這條資料已被上了X鎖,因此事務b的查詢操作必須得等到該資料的X鎖被釋放,才有機會執行查詢操作。如此,就避開了髒讀問題。而不可重複讀問題還是存在的,繼續上面的不可重複讀的例子。如果事務a想要執行查詢操作,那麼得先給該資料加S鎖,沒問題,加鎖後執行了查詢操作,讀到的是(1,‘a’),讀完後就釋放了S鎖。這時事務b要執行修改操作,那麼得先該記錄加X鎖,由於現在該記錄沒被加任何鎖,因此事務b可以給該記錄加X鎖,然後執行了更新操作,將記錄為(1,‘a’)的記錄改成了(1,‘b’),事務b結束釋放X鎖。然後事務a進行驗算操作,正常加上S鎖然後再查詢一次,發現記錄變成了(1,‘b’)。那麼事務a兩次讀到的記錄是不一樣的結果,因此,不可重複讀的問題還是存在。

之所以不可重複讀的問題會存在,就是因為,二級封鎖協議規定了,在查詢完之後就會釋放S鎖。如此一來,在事務a的兩次查詢中間,就讓事務b的修改操作有機可趁了。

三級封鎖協議

三級封鎖協議是指,在以及封鎖協議的基礎上增加事務T在讀取資料R之前必須先對其加S鎖,直到事務結束才釋放。

三級封鎖協議是在一級封鎖協議的基礎上的,因此必然能解決丟失更新問題,另外,三級封鎖協議規定,在增加事務T在讀取資料R之前必須先對其加S鎖,這和二級封鎖協議一樣,因此三級封鎖協議也能解決髒讀問題。除此之外,它還規定加上的S鎖直到結束才釋放。這就解決了不可重複讀問題。繼續以上面的例子來說。如果事務a想要執行查詢操作,那麼得先給該資料加S鎖,沒問題,加鎖後執行了查詢操作,讀到的是(1,‘a’),讀完後沒有立馬釋放S鎖。這時事務b想對該記錄進行修改,那麼先加R鎖吧,發現,該記錄已經被上了S鎖,因此,沒辦法加R鎖,因此只能一直等待,直到S鎖被釋放。如此就保證了事務a的兩次查詢之間不會插入另一個事務對同一記錄的更新操作。這樣一來,事務a兩次讀取的記錄就一定會是一樣的。如此,就避免了不可重複讀問題。

封鎖協議

這時再回過頭來看一級封鎖協議,以及再來理解讀取記錄和對記錄加鎖的關係,就應該能理解了吧。一個事務的讀取操作是否需要等待取決於使用的封鎖協議。如果使用的是一級封鎖協議,那麼對讀取操作沒有加S鎖的限定,因此讀取是自由的。而在二級封鎖協議一級三級封鎖協議中,對讀取操作是要求先對所要讀取的記錄加S鎖。因此,讀取並不是自由的。

四級封裝協議說我自己想的,書上沒有,我想如果定義四級封裝協議,就是規定:在讀取和修改記錄之前,要先要操作該記錄加上R鎖。

封鎖級別越高,能解決的問題就越多。但是,即使是三級封鎖協議,還是沒能解決幻讀問題。不過,一些情況下,些許的併發問題是能容忍的,因此我們並不要求資料庫沒有一丁點的併發問題。因為封鎖級別越高其併發效率就越低。

單純的封鎖協議會導致活鎖問題和死鎖問題,活鎖問題可以通過先來先服務的策略來避免。而死鎖問題則複雜點,得通過死鎖預防和死鎖解除。這裡由於自身也沒什麼瞭解就不展開了。

可以近似的把事務隔離程度和封鎖協議對應起來,這裡只是近似,近似,事務隔離級別是處理了死鎖活鎖問題的。
一級封鎖協議,對應著read_uncommited,
二級封鎖協議,對應著read_commited,
三級封鎖協議,對應著repeatable_read,
四級封鎖協議,對應著serilizable,

最後

事務這一塊,要展開還會有很多東西的。封鎖技術只是併發控制的技術之一,除此之外,還有時間戳、樂觀控制閥、多版本併發控制即MVCC等等技術。但暫時就先這樣吧。

看了許多文章,都沒有解釋為什麼一級封鎖協議可以解決丟失更新問題。而只是作為結論拿來用。那我這篇文章在這一點上,也算是有亮點吧。當然了,雖然我想到了為什麼,但是自己卻沒想明白為什麼,因此還得感謝這篇文章:百度知道

嗯,凡事多問個為什麼吧。一些看似覺得懂的東西,也許問問自己為什麼,就發現原來自己根本就沒懂。

資料庫我很渣很渣,而且我也表述、邏輯也不是很清晰。如果看官看到有哪裡寫的不對的,或者覺得哪裡可以修改一下,希望能幫忙指出,在此謝過了。

參考文章

寫這篇文章之前,先是看了許多篇別人的文章,在有了一定的瞭解的基礎上再寫的,並且在寫的過程中,一遍查書,一遍上網查。