出自:https://blog.csdn.net/u014439239/article/details/78086729
數據庫事務有不同的隔離級別,不同的隔離級別對鎖的使用是不同的,鎖的應用最終導致不同事務的隔離級別。
隔離性分為四個級別:
1讀未提交:(Read Uncommitted)
2讀已提交(Read Committed) 大多數數據庫默認的隔離級別
3可重復讀(Repeatable-Read) mysql數據庫所默認的級別
4序列化(serializable)
四個級別的具體實現和不同的請下面細讀:
首先程序是可以並發執行的,同樣,在MySQL中,一個表可以由兩個或多個進程同時來讀寫數據,這是沒有問題的。
比如,此時有兩個進程來讀數據,這也沒什么問題,允許。但是如果一個進程在讀某一行的數據的過程中,另一個在進程又往這一行里面寫數據(改、刪),那結果會是如何?同樣,如果兩個進程都同時對某一行數據進行更改,以誰的更改為准?那結果又會怎樣,不敢想象,是不是數據就被破壞掉了。所以此時是沖突的。
既然會沖突就要想辦法解決,靠誰來解決,這時候就是靠鎖機制來維護了。怎么使用鎖來使他們不沖突?
在事務開始的時候可以給要准備寫操作的這一行數據加一個排它鎖,如果是讀操作,就給該行數據一個讀鎖。這樣之后,在修改該行數據的時候,不讓其他進程對該行數據有任何操作。而讀該行數據的時候,其他進程不能更改,但可以讀。讀或寫完成時,釋放鎖,最后commit提交。這時候讀寫就分離開了,寫和寫也就分離開了。
注意:此時加鎖和釋放鎖的過程由mysql數據庫自身來維護,不需要我們人為干涉。mysql開發者給這個解決沖突的方案起了一個名字叫做:讀未提交:(Read Uncommitted)。這也就是事務的第一個隔離性。
但是這個程度的隔離性僅僅是不夠的。看下面的測試結果:
1)A修改事務級別為:未提交讀。並開始事務,對user表做一次查詢

2)B事務更新一條記錄

3)此時B事務還未提交,A在事務內做一次查詢,發現查詢結果已經改變

4)B進行事務回滾

5)A再做一次查詢,查詢結果又變回去了

由試驗得知:在一個進程的事務當中,我更改了其中的一行數據,但是我修改完之后就釋放了鎖,這時候另一個進程讀取了該數據,此時先前的事務是還未提交的,直到我回滾了數據,另一個進程讀的數據就變成了無用的或者是錯誤的數據。我們通常把這種數據叫做臟數據,這種情況讀出來的數據叫做賍讀。
怎么辦?依然是靠鎖機制。無非是鎖的位置不同而已,之前是只要操作完該數據就立馬釋放掉鎖,現在是把釋放鎖的位置調整到事務提交之后,此時在事務提交前,其他進程是無法對該行數據進行讀取的,包括任何操作。那么數據庫為此種狀態的數據庫操作規則又給了一個名字叫做:讀已提交(Read Committed),或者也可以叫不可重復讀。這也就是事務的第二個隔離性。
在某些情況下,不可重復讀並不是問題,比如我們多次查詢某個數據當然以最后查詢得到的結果為主。但在另一些情況下就有可能發生問題,例如對於同一個數據A和B依次查詢就可能不同,A和B就可能打起來了……
繼續看下面的測試結果:
1)把隔離性調為READ-COMMITTED(讀取提交內容)設置A的事務隔離級別,並進入事務做一次查詢

2)B開始事務,並對記錄進行修改

3)A再對user表進行查詢,發現記錄沒有受到影響

4)B提交事務

5)A再對user表查詢,發現記錄被修改

試驗進行到這里,你會發現,在同一個事務中如果兩次讀取相同的數據時,最后的結果卻不一致。這里我們把這種現象稱為:不可重復讀。因為在第一個事務讀取了數據之后,此時另一個事務把該數據給修改了,這時候事務提交,那么另一個事務在第二次讀取的時候,結果就不一樣,一個修改前的,一個是修改后的。
但是細心的你會發現,既然你說此種隔離性是在事務提交后才釋放鎖,那么在試驗過程中,在該數據未提交前,另一個事務為什么也是仍然可以讀取的呀。是我說錯了嗎?不是的,在這里mysql使用了一個並發版本控制機制,他們把它叫做MVCC,通俗的也就是說:mysql為了提高系統的並發量,在事務未提交前,雖然事務內操作的數據是鎖定狀態,但是另一個事務仍然可以讀取,大多數數據庫默認的就是這個級別的隔離性。但mysql不是。
而且不只是在更新數據時出現這個問題,在插入數據時仍然會造成類似的這樣一種現象:mysql雖然鎖住了正在操作的數據行,但它仍然不會阻止另一個事務往表插入新行新的數據。比如:一個事務讀取或更新了表里的所有行,接者又有另一個事務往該表里插入一個新行,在事務提交后。原來讀取或更改過數據的事務又第二次讀取了相同的數據,這時候這個事務中兩次讀取的結果集行數就不一樣。原來更新了所有行,而現在讀出來發現竟然還有一行沒有更新。這就是所謂的幻讀。
為了防止同事務中兩次讀取數據不一致,(包括不可重讀和幻讀),接下來該如何繼續做呢?!
mysql依然采取的是MVCC並發版本控制來解決這個問題。具體是:如果事務中存在多次讀取同樣的數據,MySQL第一次讀的時候仍然會保持選擇讀最新提交事務的數據,當第一次之后,之后再讀時,mysql會取第一次讀取的數據作為結果。這樣就保證了同一個事務多次讀取數據時數據的一致性。這時候,mysql把這種解決方案叫做:可重復度(Repeatable-Read),也就是上述所寫的第三個隔離性,也是mysql默認的隔離級別。
注意:幻讀和不可重復讀(Read Committed)都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重復讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
說到這里,真的就完事了嗎?到這里其實mysql並未完全解決數據的一致性問題。只是在讀取上做了手腳,解決了傳統意義上的幻讀和不可重復讀。
例子:1 A事務開啟,B事務開啟。
2 B事務往表里面插入了一條數據,但還並未提交。
3 A事務開始查詢了,並沒有發現B事務這次插入的數據。然后此時B事務提交了數據。
4 於是乎,A事務就以為沒有這條數據,就開始添加這條數據,但是卻發現,發生了數據 重復沖突。
最后這個時候,該我們的最后一種隔離級別也是最高的隔離級:別序列化(serializable)登場了。
該隔離級別會自動在鎖住你要操作的整個表的數據,如果另一個進程事務想要操作表里的任何數據就需要等待獲得鎖的進程操作完成釋放鎖。可避免臟讀、不可重復讀、幻讀的發生。當然性能會下降很多,會導致很多的進程相互排隊競爭鎖。
后記:以上所說的四種隔離性的鎖機制應用是數據庫自動完成的,不需要人為干預。隔離級別的設置只對當前鏈接有效。對於使用MySQL命令窗口而言,一個窗口就相當於一個鏈接,當前窗口設置的隔離級別只對當前窗口中的事務有效。
