事务隔离机制原理深入分析以及MySQL不同隔离级别分场景下实验对比
這是我總結的事務的四種隔離機制,比較好理解,主要是有些地方文字游戲說不清楚很容易混淆:
Read Uncommitted(讀未提交)A未完,B已更新,未提交,A讀到B已更新的數據,由于未提交,那么可能會回滾,所以這樣的數據就是錯誤的數據也就是臟讀。
Read Committed(讀已提交)A未完,B已更新,已提交,A讀到B已更新且提交的數據,由于已提交,所以是正確的數據,但是可能這個事務比較長,讀幾次都不一樣,從業務上說也許對也許不對,所以這個是不可重復讀的。
Repeatable Read(可重復讀)A未完,B不一定可以更新,不管B有無更新/提交,A讀不到B的數據(MYSQL默認),自己每次讀都是一樣的,但是存在幻讀,幻讀是期間發送新增刪除這樣的操作導致。
Serializable(串行化),絕對無錯。
-------------
MySQL事務學習總結
關于幻讀,網上很多描述都是錯誤的。
幻讀是指讀到了其它事務提交的新增數據,
不可重復讀是指讀到了其他事務提交的更改數據(更改或刪除)。
為了解決不可重復讀,只需要通過行級鎖來實現就可以了,但是為了解決幻讀,則不能僅僅鎖住一條數據,因為這樣的鎖不能阻止別的事務新增記錄,MySQL用了間隙鎖來解決這個問題,而不是表級鎖。
InnoDB實現的是MVCC,MVCC中,讀分為快照讀和當前讀
MySQL加鎖處理分析@何登成的技術博客
快照讀(簡單的select操作,屬于快照讀,不加鎖):
select * from test where money = 500;
當前讀(特殊的讀操作,插入/更新/刪除操作,屬于當前讀,需要加鎖):
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
可能會覺奇怪,為什么insert update delete也屬于當前讀,因為針對這些操作,都是InnoDB先把數據篩選出來,加鎖,把數據交給MySQL Server處理,Server處理好后交給InnoDB更新,然后InnoDB釋放鎖,這里面就有讀的操作。
這里說的幻讀指的是當前讀。這是登博關于幻讀的解釋,所謂幻讀,就是同一個事務,連續做兩次當前讀 (例如:select?from t1 where id = 10 for update;),那么這兩次當前讀返回的是完全相同的記錄 (記錄數量一致,記錄本身也一致),第二次的當前讀,不會比第一次返回更多的記錄 (幻象)。
-----
MySQL數據庫間隙鎖
當我們用范圍條件而不是相等條件檢索數據,并請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對于鍵值在條件范圍內但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
舉例來說,假如user表中只有101條記錄,其empid的值分別是 1,2,...,100,101,下面的SQL:
select * from??user where user_id > 100 for update;
是一個范圍條件的檢索,InnoDB不僅會對符合條件的user_id值為101的記錄加鎖,也會對user_id大于101(這些記錄并不存在)的“間隙”加鎖。
InnoDB使用間隙鎖的目的,一方面是為了防止幻讀,以滿足相關隔離級別的要求,對于上面的例子,要是不使用間隙鎖,如果其他事務插入了user_id大于100的任何記錄,那么本事務如果再次執行上述語句,就會發生幻讀;另外一方面,是為了滿足其恢復和復制的需要
---------------------?
下面MySQL書《高性能MySQL(第3版)》:
隔離機制的比較
?
其實也有人喜歡用鎖來控制并發,書中還提到了“隱式”和“顯示鎖定”,是這么建議的:
?
雖然這樣,但是其實如果不經過實際的演練還是很難理解上面說的事務隔離機制到底怎么樣可以防止并發。
1.查看MySQL版本
我們的版本是5.1.7
?
2.查看存儲引擎
>show engines;
存儲引擎是:InnoDB
?
3.實驗表
假設有個商品表g,關鍵字段num表示庫存,name表示商品名稱
主要就是看不同事務隔離機制下并發修改庫存是否會出現超賣。
假設我們的程序需要先查詢庫存,如果庫存>0都可以賣,update扣庫存,否則rollback。
為了制造并發肯定需要2個事務,假設是A和B。
?
4.確認事務隔離機制
修改會話的事務隔離級別
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;
>select @@global.tx_isolation,@@tx_isolation;
?
隔離級別1:Serializable 串行化
場景一:
顯然一開始AB查詢的數據是一樣的num=1
?
A開始update
這時候在等待,無法update。
?
過一會就超時了。
?
如果這個時候B也update那么一樣會等待超時
所以這樣,AB就會都超時。
?
這時即使commit也是返回0,數據庫不會變化。
?
場景二:
A在update等待的時候,B馬上commit,但是B沒有update
?
查看結果
這次A成功的扣庫存。
?
所以從上面可以得出一個結論:serializable是可以很好的控制并發。
然后需要把庫存改為1,便于測試。
?
隔離級別2:read committed 讀已提交
>set session transaction isolation level read committed;
>select @@global.tx_isolation,@@tx_isolation;
?
場景一:
初始化AB查出來的庫存都是1,然后A可以update一條數據,無等待。
?
這時候AB再比較下庫存,A已經是0,B是1,因為A沒有commit。
?
然后A執行commit操作,這時候B再查已經是庫存0;
?
這時候B執行update返回是0行,因為update不能滿足where條件,所以B只有Commit,然后重新提交。
?
場景二:
一開始AB都是一樣的庫存1,然后A開始update,然后A的庫存是0,B是1,因為A還沒有提交。
?
這時候B再update
?
按照前面的經驗,B等待其實是再等A提交,A如果一直不提交,B就會超時。
?
這時A提交commit,B查詢就得到A更新后的結果,這時B查到庫存是0自然不會去更新,也就只能結束事務。
?
場景三:
AB先后update,然后A在B超時之前commit,這時由于B已經讀到A更新后的結果0,所以B就不能成功update。
?
隔離級別3.repeatable read 可重復讀
>set session transaction isolation level repeatable read;
>select @@global.tx_isolation,@@tx_isolation;
?
場景一:
然后A開始update,然后A和B分別讀到庫存是1和0
?
然后A提交commit,這時候再查看A和B的庫存還是保持不變。
?
這時候B再次嘗試update
依然是返回0條,說明更新不成功。
?
場景二:
AB同時update
如果A不及時commit那么B肯定會超時
?
如果A及時commit
?
所以可以看出無論是read committed還是repeatable read只要update的條件where ?num>0足夠充分都是可以控制并發防止超賣的。
如果沒有帶where ?num>0這個控制條件,那么肯定會可以update成功的。
?
隔離級別4.read uncommitted
這個是需要杜絕的,就不討論了。
?
如果沒有帶where ?num>0,那么會怎么樣呢。其實只要理解了上述流程就可以想明白會怎么樣。
對于read committed
A已經update,B讀到庫存是0自然不會去更新;
A沒有update,B讀到庫存是1,這要看A會不會及時提交;
?
如果A及時提交,B自然會去更新因為滿足where條件,且成功,這樣就超賣-1;
?
這時候由于B沒有提交,所以AB分別查出0和-1
然后B提交commit,AB查出的都是-1,就不演示了。
?
修改會話為repeatable read
AB先后update,B在等待
?
然后A立即提交commit,B馬上update得到返回。
結果就是-1產生了超賣:
?
總結:
1.使用serializable是可以防止超賣,但是性能怎么樣需要數據說明;
2.read committed和repeatable read帶上where條件庫存num>0都是可以防止超賣的,不過需要處理超時。
3.其他各種組合情況還會更復雜,具體具體問題具體分析。
?
forupdate實驗
A開始?:
B開始:?
?
擴展閱讀:
《數據庫事務的四大特性以及事務的隔離級別》
事務的四個特征ACID
⑴ 原子性(Atomicity)
原子性是指事務包含的所有操作要么全部成功,要么全部失敗回滾,這和前面兩篇博客介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。
⑵ 一致性(Consistency)
一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之后都必須處于一致性狀態。
拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉賬,轉幾次賬,事務結束后兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
⑶ 隔離性(Isolation)
隔離性是當多個用戶并發訪問數據庫時,比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個并發事務之間要相互隔離。
即要達到這么一種效果:對于任意兩個并發的事務T1和T2,在事務T1看來,T2要么在T1開始之前就已經結束,要么在T1結束之后才開始,這樣每個事務都感覺不到有其他事務在并發地執行。
關于事務的隔離性數據庫提供了多種隔離級別。
⑷ 持久性(Durability)
持久性是指一個事務一旦被提交了,那么對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。
------------
事務隔離級別
三種讀現象:
⑴ 臟讀
臟讀是指在一個事務處理過程里讀取了另一個未提交的事務中的數據。
當一個事務正在多次修改某個數據,而在這個事務中這多次的修改都還未提交,這時一個并發的事務來訪問該數據,就會造成兩個事務得到的數據不一致。
⑵不可重復讀
不可重復讀是指在對于數據庫中的某個數據,一個事務范圍內多次查詢卻返回了不同的數據值,這是由于在查詢間隔,被另一個事務修改并提交了。
例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據并且提交事務給數據庫,事務T1再次讀取該數據就得到了不同的結果,發送了不可重復讀。
不可重復讀和臟讀的區別是,臟讀是某一事務讀取了另一個事務未提交的臟數據,而不可重復讀則是讀取了前一事務提交的數據。
在某些情況下,不可重復讀并不是問題,比如我們多次查詢某個數據當然以最后查詢得到的結果為主。但在另一些情況下就有可能發生問題,
⑶ 幻讀
幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個數據項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值還是為“1”并且提交給數據庫。而操作事務T1的用戶如果再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺一樣,這就是發生了幻讀。
幻讀和不可重復讀都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重復讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
MySQL數據庫的四種隔離級別:
⑴ Serializable (串行化):可避免臟讀、不可重復讀、幻讀的發生。
⑵?Repeatable read (可重復讀):可避免臟讀、不可重復讀的發生。
⑶ Read committed (讀已提交):可避免臟讀的發生。
⑷ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別為Repeatable read (可重復讀)。
在MySQL數據庫中,支持上面四種隔離級別,默認的為Repeatable read (可重復讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的為Read committed級別。
總結
以上是生活随笔為你收集整理的事务隔离机制原理深入分析以及MySQL不同隔离级别分场景下实验对比的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 箱货多少钱啊?
- 下一篇: MySQL常用性能分析方法-profil