事務併發訪問引起的問題有如下常見的:
- 更新丟失——mysql所有事務隔離級別在數據庫層面上均可避免
- 髒讀——髒讀就是允許讀取其他事務未提交的數據,READ-COMMITTED事務隔離級別以上可避免(RC級別)。
- 不可重複讀——不可重複讀就是事務A多次讀取事務B,過程中事務B有更新操作,導致事務A讀取的數據不一樣,REPEATABLE-READ事務隔離級別以上可避免(RR級別)。
- 幻讀——指的是一個事務在前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到過的數據行,就好像發生了幻覺一樣,破壞了事務的一致性。SERIALIZABLE事務隔離級別可避免,但是會降低事務併發能力。在可重複讀隔離級別下(RR),也是可以解決幻讀的(但是他的根本職能是解決不可重複讀)
一、更新丟失
注:文中的事務就是會話,也是查詢,兩個事務就開兩個查詢即可
對於更新丟失的情況我們用一張圖表示
可以看到事務A期望的結果應該是1100元,但是實際結果卻是1000元,也就是說事務B的更新丟失了。
但是MySQL所有的隔離級別都能避免更新丟失情況。我們用一個測試說明,建表如下:
開啓兩個查詢模擬兩個事務,這兩個事務在操作前需要關閉自動提交:set autocommit = 0;
;將隔離級別開到最低的RU級別:set session transaction isolation level read uncommitted;
兩個都要開始事務:start TRANSACTION;
先是事務1的操作:
# 查詢一號的餘額,結果爲1000
SELECT balance FROM account_innodb where id = 1;
然後事務2開始查詢,並操作餘額
# 查詢一號的餘額,結果爲1000
SELECT balance FROM account_innodb where id = 1;
# 將一號的餘額+100
update account_innodb set balance=1000+100 where id = 1;
# 再次查詢餘額爲1100
SELECT balance FROM account_innodb where id = 1;
# 提交事務
COMMIT;
然後事務1進行操作:
update account_innodb set balance=balance-100 where id = 1;
結果:
此時並沒有提交事務,我們撤銷操作,進行回滾,然後查詢1號的餘額
# 事務回滾,即關閉事務
ROLLBACK;
# 查詢餘額
SELECT balance FROM account_innodb where id = 1;
可以看到結果是事務2存入錢後的餘額,其他的隔離級別下的更新丟失不再演示
二、髒讀
- 開頭我們仍然以上邊的兩個會話設置爲基礎,即隔離級別最低,我們看看是不是會發生讀取到未提交的事務數據
- 首先之前兩個會話都要commit,保證事務已經結束。下邊開始
兩個會話開啓事務:start TRANSACTION;
然後update account_innodb set balance = balance -100 where id = 1;
,更改後的結果爲1000,但是我們沒有提交,數據庫裏面並沒有真正進行更改,未提交的數據存在redo log文件中,另一個事務查詢的話,我們期望的是還是數據庫中那個沒有變動的數據,我們來測試會話2:
# start TRANSACTION;沒有開啓事務的記得開啓
SELECT balance FROM account_innodb where id = 1;
可以看到結果爲1000,發生髒讀了。
如何避免呢?開啓RC級別及以上就可以了
# 設置成能防止髒讀的read COMMITTED,再按照之前步驟進行測試
set session transaction isolation level read committed;
按照之前來一次
結果顯示正常了
三、不可重複讀
- mysql的默認隔離級別爲 repeatable read(RR),該級別就能避免不可重複讀。事務1對某一行記錄會多次讀取,在這些過程中,事務2進來修改了該記錄,但是事務1並沒有結束事務,然後,事務1再次讀取,讀到的是事務2提交的結果,這明顯不利於當前事務的執行。
我們先基於髒讀測試的基礎上進行測試,即隔離級別爲RC,之前的事務都提交,然後重新開啓事務
事務1開啓 事務之後執行以下語句
SELECT balance FROM account_innodb where id = 1;
然後事務2執行如下
update account_innodb set balance = balance +200 where id = 1;
# 並提交
commit;
回到事務1
# 再次查詢,期望爲1100
SELECT balance FROM account_innodb where id = 1;
結果
說明RC及以下無法避免不可重複讀
我們將隔離級別切換到repeatable read級別,將兩個會話都切換成RR級別:
set session transaction isolation level repeatable read;
然後再按照上邊的步驟測試,上邊的結果是1300,那麼這次事務1最終結果應該是1300,測試結果爲:1300
說明避免了
四、幻讀
基於上邊的測試的基礎,隔離級別改變爲 read uncommitted,保證事務都已經提交
事務1
# 開啓事務後執行範圍操作,並上共享鎖,鎖住查詢到的記錄行
SELECT balance FROM account_innodb lock in share mode;
事務2在其之後插入一條數據(可以插入,鎖住4行都是行鎖,不影響新添數據行)
# 開啓事務後執行
insert into account_innodb values ('test5',1200);
# 提交
commit;
查看account_innodb表
然後我們用事務1進行全範圍更新
# 將工資全部改爲1000
update account_innodb set balance=1000;
# 查詢是否是4條1000的記錄
SELECT balance FROM account_innodb;
很匪夷所思吧,五條記錄都被更新(對於事務1來說,不是應該是4條嗎?),跟產生幻覺一樣,說明在RC級別下產生了幻讀。
4.1、RR級別下測試
我們將兩個會話隔離級別切換爲RR:set session transaction isolation level read COMMITTED;
然後按照之前的方法測試,也是事務1線進行查詢,然後事務2進行添加操作,奇怪的事發生了
被阻塞了,說明RR級別可以避免幻讀,這是因爲mysql採用了gap lock,詳情請見數據庫之InnoDB可重複讀隔離級別下如何避免幻讀
4.2、SERIALIZABLE級別下測試
切換級別set session transaction isolation level SERIALIZABLE;
,回滾之前事務ROLLBACK;
,按照RR級別下的測試來一遍,到了事務2進行插入的時候:
五、具體對比圖
如有問題,請及時指出