數據庫之事務併發訪問引起的問題以及如何避免


事務併發訪問引起的問題有如下常見的:

  • 更新丟失——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進行插入的時候:
在這裏插入圖片描述

五、具體對比圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oAahu7SI-1587702400298)(C:\Users\Taogege\AppData\Roaming\Typora\typora-user-images\image-20200422182835697.png)]

如有問題,請及時指出

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