總結
- read uncommited 未提交讀:是最低的事務隔離級別。事務未提交前,數據就可被其他事務讀取 --> 會造成:臟讀、不可重復讀、幻讀
- read commited 提交讀:一個事務提交后才能被其他事務讀取到,SQL server 的默認級別 --> 會造成:不可重復讀、幻讀
- repeatable read 可重復讀:保證多次讀取同一個數據時,其值都和事務開始時候的內容是一致,禁止讀取到別的事務未提交的數據,MySQL 的默認級別 --> 會造成: 幻讀。
- serializable 序列化:這是花費最高代價但最可靠的事務隔離級別。事務被處理為順序執行。該隔離級別能防止臟讀、不可重復讀、幻讀。
不可重復讀 vs 幻讀
設置repeatable read隔離后:事務A要兩次讀取表T的中數據,雖然設置 repeatable read 可以防止事務B對數據進行修改(修復可重復讀),
但是事務B卻可以向表T中插入新的數據(依然無法進制幻讀)。
二、臟讀、不可重復讀、幻象讀
- 臟讀:表示一個事務能夠讀取另一個事務中還未提交的數據。比如,某個事務嘗試插入記錄 A,此時該事務還未提交,然后另一個事務嘗試讀取到了記錄 A。因為這個記錄 A還沒有提交那么另外一個事務讀取到的這個數據我們稱之為臟數據。依據臟數據所做的操作肯能是不正確的。
- 不可重復讀:指在一個事務內,多次讀同一數據,但不一樣。例如,事務1 讀取 記錄A, 事務1 還沒有執行結束;此時另外一個 事務2 也訪問 記錄A,並修改了記錄A。 那么在事務1 第兩次讀取記錄A時,讀到的數據可能是不一樣的。這種情況被稱為是不可重復讀。
- 幻象讀:指同一個事務內多次查詢返回的結果集不一樣。比如同一個事務 A 第一次查詢時候有 n 條記錄,但是第二次同等條件下查詢卻有 n+1 條記錄,這就好像產生了幻覺。發生幻讀的原因也是另外一個事務B新增或者刪除或者修改了第一個事務A結果集里面的數據,同一個記錄的數據內容被修改了,所有數據行的記錄就變多或者變少了。因此幻讀的必要條件是兩個,第一是事務B有Insert/delete操作,第二個另一個事務A做了范圍查詢;
三、詳細文章
好久沒碰數據庫了,只是想起自己當時在搞數據庫的時候在事務隔離級別這塊老是卡,似懂非懂的。現在想把這塊整理出來,盡量用最簡潔的語言描述出來,供新人參考。
首先創建一個表account。創建表的過程略過(由於InnoDB存儲引擎支持事務,所以將表的存儲引擎設置為InnoDB)。表的結構如下:

然后往表中插入兩條數據,插入后結果如下:

為了說明問題,我們打開兩個控制台分別進行登錄來模擬兩個用戶(暫且成為用戶A和用戶B吧),並設置當前MySQL會話的事務隔離級別。
3.1 read uncommitted(讀取未提交數據)
具體用戶A的操作如下:
set session transaction isolation level read uncommitted; start transaction; select * from account;
結果如下:

用戶B的操作如下:
set session transaction isolation level read uncommitted;
start transaction;
update account set account=account+200 where id = 1;
隨后我們在A用戶中查詢數據,結果如下:

結論一:
我們將事務隔離級別設置為read uncommitted,即便是事務沒有commit,但是我們仍然能讀到未提交的數據,這是所有隔離級別中最低的一種。
那么這么做有什么問題嗎?
那就是我們在一個事務中可以隨隨便便讀取到其他事務未提交的數據,這還是比較麻煩的,我們叫臟讀。我不知道這個名字是怎么起的,為了增強大家的印象,可以這么想,這個事務好輕浮啊,飢渴到連別人沒提交的東西都等不及,真臟,呸!
實際上我們的數據改變了嗎?
答案是否定的,因為只有事務commit后才會更新到數據庫。
3.2 read committed(可以讀取其他事務提交的數據)---大多數數據庫默認的隔離級別
同樣的辦法,我們將用戶B所在的會話當前事務隔離級別設置為read commited。
在用戶A所在的會話中我們執行下面操作:
update account set account=account-200 where id=1;

我們將id=1的用戶account減200。然后查詢,發現id=1的用戶account變為800。
在B用戶所在的會話中查詢:
select * from account;
結果如下:

我們會發現數據並沒有變,還是1000。
接着在會話A中我們將事務提交:
commit;
在會話B中查詢結果如下:

結論二:
當我們將當前會話的隔離級別設置為read committed的時候,當前會話只能讀取到其他事務提交的數據,未提交的數據讀不到。
那么這么做有什么問題嗎?
那就是我們在會話B同一個事務中,讀取到兩次不同的結果。這就造成了不可重復讀,就是兩次讀取的結果不同。這種現象叫不可重復讀。
3.3 repeatable read(可重讀)---MySQL默認的隔離級別
(注意:原文這部分的例子有誤,遂刪除)
結論三:
當我們將當前會話的隔離級別設置為repeatable read的時候,當前會話可以重復讀,就是每次讀取的結果集都相同,而不管其他事務有沒有提交。
有什么問題嗎?
管他呢,老板的要求滿足了。要一個事務中讀取的數據一致(可重復讀)。我只能這么做啊,打腫臉裝胖子。數據已經發生改變,但是我還是要保持一致。但是,出現了用戶B面對的問題,這種現象叫幻讀(記得當時就在這個地方糾結好久,到底什么是幻讀啊)。
3.4 serializable(串行化)
同樣,我們將用戶B所在的會話的事務隔離級別設置為serializable並開啟事務。
set session transaction isolation level serializable;
start transaction;
在用戶B所在的會話中我們執行下面操作:
select * from account;
結果如下:

那我們這個時候在用戶A所在的會話中寫數據呢?

我們發現用戶A所在的會話陷入等待,如果超時(這個時間可以進行配置),會出現Lock wait time out提示:

如果在等待期間我們用戶B所在的會話事務提交,那么用戶A所在的事務的寫操作將提示操作成功。
結論四:
當我們將當前會話的隔離級別設置為serializable的時候,其他會話對該表的寫操作將被掛起。可以看到,這是隔離級別中最嚴格的,但是這樣做勢必對性能造成影響。所以在實際的選用上,我們要根據當前具體的情況選用合適的。
參考文獻
作者:傘U
鏈接:https://www.jianshu.com/p/4e3edbedb9a8
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。