MySQL事務隔離級別&鎖機制分析論證

基本概念

1、事務隔離級別說明
ANSI/ISO SQL標準定義了4中事務隔離級別:未提交讀(read uncommitted),提交讀(read committed),重複讀(repeatable read),串行讀(serializable)。
對於不同的事務,採用不同的隔離級別分別有不同的結果。不同的隔離級別有不同的現象。主要有下面3種現在:
a、髒讀(dirty read):一個事務可以讀取另一個尚未提交事務的修改數據。
b、不可重複讀(nonrepeatable read):在同一個事務中,同一個查詢在T1時間讀取某一行,在T2時間重新讀取這一行時候,這一行的數據已經發生修改,可能被更新了(update),也可能被刪除了(delete)。
b、幻像讀(phantom read):在同一事務中,同一查詢多次進行時候,由於其他插入操作(insert)的事務提交,導致每次返回不同的結果集。
不同的隔離級別有不同的現象,並有不同的鎖定/併發機制,隔離級別越高,數據庫的併發性就越差,4種事務隔離級別分別表現的現象如下表:
隔離級別 髒讀 非重複讀 幻像讀
read uncommitted 允許 允許 允許
read committed   允許 允許
repeatable read     允許
serializable      

2、鎖機制說明:
共享鎖:由表操作加上的鎖,加鎖後其他用戶只能獲取該表或行的共享鎖,不能獲取排它鎖,也就是說只能讀不能寫
排它鎖:由表操作加上的鎖,加鎖後其他用戶不能獲取該表或行的任何鎖,也可以理解爲表或行鎖定。典型是mysql事務中
start transaction;
select * from user where userId = 1 for update;
執行完這句以後
  1)當其他事務想要獲取共享鎖,比如事務隔離級別爲SERIALIZABLE的事務,執行
   select * from user;
   將會被掛起,因爲SERIALIZABLE的select語句需要獲取共享鎖
  2)當其他事務執行
   select * from user where userId = 1 for update;
   update user set userAge = 100 where userId = 1; 
  也會被掛起,因爲for update會獲取這一行數據的排它鎖,需要等到前一個事務釋放該排它鎖纔可以繼續進行
特別注意:如果for update 前的where 並非主鍵過濾則爲表級排它鎖,僅有過濾參數爲=ID主鍵時方爲行級排它鎖) 

3、鎖的範圍:
行鎖: 對某行記錄加上鎖
表鎖: 對整個表加上鎖
這樣組合起來就有,行級共享鎖,表級共享鎖,行級排他鎖,表級排他鎖

4、悲觀鎖與悲觀鎖
悲觀鎖:顯式爲數據加鎖,常見有如下兩種加鎖方式 
  1. 顯式指定獨佔鎖:select … for update
  2. 在數據庫增加表明狀態的LOCK字段
樂觀鎖:通過版本控制實現,這種方式我們能夠更好地提高併發事務的性能。

驗證REPEATABLE-READ的實例效果

使用InnoDB,開啓兩個客戶端SESSION1、SESSION2
1、通過SELECT @@GLOBAL.tx_isolation, @@tx_isolation;查看默認的事務隔離級別:

如果我們修改當前session修改其事務隔離級別,可以通過如下方式:


2、REPEATABLE-READ隔離級別驗證:
  SESSION1 SESSION2
T1 mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
T2 mysql> use icbc;
Database changed
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 222.00 | card001 |
+--------+----------+
1 row in set (0.11 sec)
mysql> use icbc;
Database changed
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 222.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
T3 mysql> update t_eventlog set amount=111 where fromcard='card001';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
 
T4 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 111.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】session1能夠讀取自己修改的最新數據
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 222.00 | card001 |
+--------+----------+
1 row in set (0.12 sec)
【說明】amount不變,session1中修改數據事務未提交,並未出現髒讀
T5   mysql> update t_eventlog set amount=333 where fromcard='card001';
【說明】session2修改直接被掛起,因爲session1事務未提交
T6 mysql> commit
    -> ;
Query OK, 0 rows affected (0.00 sec)
mysql> update t_eventlog set amount=333 where fromcard='card001';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
【說明】session1提交事務,session2本應提交修改,但超時需要重啓事務,下面加快速度重複上述T2~T6操作
T2 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 111.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 111.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
T3 mysql> update t_eventlog set amount=1 where fromcard='card001';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
 
T4 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 1.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】session1能夠讀取自己修改的最新數據
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 111.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】amount不變,session1中修改數據事務未提交,並未出現髒讀
T5   mysql> update t_eventlog set amount=2 where fromcard='card001';
【說明】session2修改直接被掛起,因爲session1事務未提交
T6 mysql> commit
    -> ;
Query OK, 0 rows affected (0.00 sec)
【說明】提交session1事務
 
T7   Query OK, 1 row affected (4.79 sec)
Rows matched: 1 Changed: 1 Warnings: 0
【說明】session1事務提交,session2執行修改數據
T8 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 1.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】amount不變,session2中修改數據事務未提交,並未出現髒讀
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
T9   mysql> commit
    -> ;
Query OK, 0 rows affected (0.00 sec)
T10 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】讀取到最新的session2提交事務數據
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
T11 mysql> show variables like 'autocommit'\G
*************************** 1. row ***************************
Variable_name: autocommit
        Value: ON
1 row in set, 1 warning (0.00 sec)
【說明】在session1中T8、T10兩次的數據不一致,是否違背了重複讀呢,其實不然,由於我們設置的事務提交是自動。
mysql> show variables like 'autocommit'\G
*************************** 1. row ***************************
Variable_name: autocommit
        Value: ON
1 row in set, 1 warning (0.00 sec)
A 以上已經證明了不會出現髒讀,只有在更新事務提交後方可被其他事務讀取修改後數據;如果兩個事務同時對某一數據行進行更新操作,如A事務先更新,則B事務被掛起,待A更新操作事務提交後,B事務執行更新操作並等待事務提交(更新操作可能出現等待超時),提交後最終數據爲B事務提交數據。其實是加上了行共享鎖,此時數據行能夠被多個事務讀取卻不能被寫。
T12 mysql> set @@autocommit = 0
    -> ;
Query OK, 0 rows affected (0.00 sec)
【說明】設置事務提交手動
mysql> set @@autocommit = 0
    -> ;
Query OK, 0 rows affected (0.00 sec)
【說明】設置事務提交手動
T13 mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
T14 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
T15   mysql> update t_eventlog set amount=3 where fromcard='card001';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
T16 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
 
T17   mysql> commit
    -> ;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 3.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】提交事務後查詢爲最新提交的事務數據
T18 mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 2.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】數據依然沒有變化,目前爲手動提交事務,查詢也需要手動提交。此時說明在一個事務內重複讀,數據是一致的。
 
T19 mysql> commit
    -> ;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT amount,fromcard FROM t_eventlog;
+--------+----------+
| amount | fromcard |
+--------+----------+
| 3.00 | card001 |
+--------+----------+
1 row in set (0.00 sec)
【說明】session1提交查看事務後重新查詢,可以看到session2提交的最新的數據
 
B 通過手動提交事務驗證了,REPEATABLE-READ級別隔離下,在一個事務內,我們多次查詢結果是一樣的,能夠重複讀。
以上的驗證過程主要是針對共享鎖實現。

綜上
1、我們在REPEATABLE-READ級別隔離下,如果多個事務同時操作一個數據行,則後執行更新操作的事務將被掛起,直到第一個更新事務提交後,後執行事務將繼續執行(可能出現掛起直到超時)其更新動作,待最後執行事務也提交後,數據庫將保存最後一個事務的更新結果。此過程中,事務間互不干擾,只有提交事務後的數據方可被其他事務讀取(避免了髒讀),並且在同一事務下,多次讀取的數據將保持一致,實現重複讀;
2、mysql有一個autocommit參數,默認是on,他的作用是每一條單獨的查詢都是一個事務,並且自動開始,自動提交(執行完以後就自動結束了,如果要適用select for update,而不手動調用 start transaction,這個for update的行鎖機制等於沒用,因爲行鎖在自動提交後就釋放了),所以事務隔離級別和鎖機制即使不顯式調用start transaction,這種機制在單獨的一條查詢語句中也是適用的;
3、其實無亂是哪個級別的隔離,甚至最低級別在一個事務(A)更新修改後,其他事務在A事務提交前均被掛起,僅A事務提交後方可進行數據行更新處理(可能超時),其均是由於A事務對其添加了行共享鎖
4、這裏雖然沒有繼續驗證,但如果採用SERIALIZABLE級別的隔離,則其查詢|更新任意操作後均會對其添加表共享鎖,從而其他事務僅可以讀操作,不能執行任何寫操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章