快速理解髒讀,不可重複讀,幻讀
轉載:https://www.cnblogs.com/scorates/p/12874937.html
介紹
要聊事務,不可避免的要提到資料庫事務的四大特性:ACID
- atomic
- consistence
- isolation
- durability
先放一個表格,看看4個隔離級別會出現的各種問題,網上的解釋一大堆。看完後還是一臉懵逼,感覺懂了,又好像沒懂。因為沒有具體的演示例子,索性自己嘗試復現這幾個問題。
√ 為會發生,×為不會發生:
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
read uncommitted(未提交讀) | √ | √ | √ |
read committed(提交讀) | × | √ | √ |
repeatable read(可重複讀) | × | × | √ |
serialization(可序列化) | × | × | × |
再總結mysql 的常用命令(下面會用到):
命令 | 含義 |
---|---|
select version() | 檢視MySQL版本 |
SELECT @@tx_isolation | 檢視MySQL隔離級別 |
set session transaction isolation level 隔離級別 | 會話層面設定隔離級別 |
start transaction | 開啟事務 |
commit | 提交事務 |
rollback | 回滾事務 |
演示
首先建立如下表:
CREATE TABLE `account` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`balance` int(3) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
- 1
- 2
- 3
- 4
- 5
- 6
用Navicat(其他工具也行)開2個查詢的tab頁,表示2個會話
首先,在2個Tab頁面分別執行SELECT @@tx_isolation
*髒讀
表中的資料如下,設定隔離級別為未提交讀
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level read uncommitted; start transaction(開啟事務); update account set balance = balance+1000 where id = 1; select * from account where id = 1; 設定為未提交讀,給張三賬號+1000,輸出為2000 |
|
T2 | set session transaction isolation level read uncommitted; start transaction; select * from account where id = 1; 查詢餘額輸出為2000 |
|
T3 | rollback | |
T4 | commit | |
T5 | select * from account where id = 1; 查詢餘額輸出為1000 |
----->舉個例子概述一下這個過程,財務給張三發了1000元的工資,然後張三查詢自己的賬戶,果然多了1000元,變成了2000元,結果財務操作過程有誤,事務回滾。當張三再查賬戶時,卻發現賬戶只有1000元。
髒讀就是指當事務A對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務B也訪問這個資料,然後使用了這個資料。
----->再舉一例,表中的資料如下:
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level read uncommitted; start transaction; update account set balance = balance-1000 where id = 1; update account set balance = balance+1000 where id = 2; |
|
T2 | set session transaction isolation level read uncommitted; start transaction; select balance from account where id = 2; update account set balance = balance -1000 where id = 2; 更新語句被阻塞 |
|
T3 | rollback | |
T4 | commit |
執行完成,資料庫中的資料如下
解釋如下:
時間 | 解釋 |
---|---|
T1 | 1給2轉賬1000 |
T2 | 2的餘額夠1000,購買1000元商品,更新語句被阻塞 |
T3 | 1回滾,1的餘額變成1000,2的餘額變成0 |
T4 | 2成功扣款,餘額為0-1000=-1000 |
如此便出故障了!
*不可重複讀
表中的資料如下,設定隔離級別為提交讀
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level read committed; start transaction; select * from account where id = 2; 查詢出餘額輸出為0; |
|
T2 | set session transaction isolation level read committed; start transaction; update account set balance = balance + 1000 where id = 2; select * from account where id = 2; commit; 查詢餘額輸出1000 |
|
T3 | select * from account where id = 2; commit; 查詢餘額輸出1000 |
不可重複讀是指在事務1內,讀取了一個數據,事務1還沒有結束時,事務2也訪問了這個資料,修改了這個資料,並提交。緊接著,事務1又讀這個資料。由於事務2的修改,那麼事務1兩次讀到的的資料可能是不一樣的,因此稱為是不可重複讀。
當然你可以在T2時間段客戶端B修改完id=2的賬戶餘額但沒有commit的時候,在客戶端A查詢id=2的賬戶餘額,發現賬戶餘額為0,可以證明提交讀這個隔離級別不會發生髒讀。
現在用上面的例子看一下可重複讀是個什麼過程?
表中的資料如下,設定隔離級別為可重複讀
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level repeatable read; start transaction; select * from account where id = 2; 查詢餘額輸出為0; |
|
T2 | set session transaction isolation level repeatable read; start transaction; update account set balance = balance + 1000 where id = 2; select * from account where id = 2; commit; 查詢餘額輸出1000 |
|
T3 | select * from account where id = 2; commit; 查詢餘額輸出0 |
仔細看這個例子和上面的例子在T3時間段的輸出,理解了什麼叫可重複讀了吧?當我們將當前會話的隔離級別設定為可重複讀的時候,當前會話可以重複讀,就是每次讀取的結果集都相同,而不管其他事務有沒有提交。
----------但是在可重複讀的隔離級別上,會產生幻讀的問題。
*幻讀
表中的資料如下,設定隔離級別為可重複讀
先上一段《高效能MySQL》對於幻讀的解釋
所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB儲存引擎通過多版本併發控制(MVCC)解決了幻讀的問題。
用大白話解釋一下,就是事務1查詢id<10的記錄時,返回了2條記錄,接著事務2插入了一條id為3的記錄,並提交。接著事務1查詢id<10的記錄時,返回了3條記錄,說好的可重複讀呢?結果卻多了一條資料。
MySQL通過MVCC解決了這種情況下的幻讀,我們可以驗證一下
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level repeatable read; start transaction; select count(*) from account where id <=10; 輸出2; |
|
T2 | set session transaction isolation level repeatable read; start transaction; insert into account(id,name,balance) values(“3”,“王五”,“0”) ; select count(*) from account where id <=10; commit; 輸出3 |
|
T3 | select count(*) from account where id <=10; commit; 輸出2 |
這種情況下的幻讀被解決了,再舉一例,表中的資料如下
時間 | 客戶端A | 客戶端B |
---|---|---|
T1 | set session transaction isolation level repeatable read; start transaction; select count(*) from account where id =3; 輸出0; |
|
T2 | set session transaction isolation level repeatable read; start transaction; insert into account(id,name,balance) values(“3”,“王五”,“0”) ; commit; |
|
T3 | insert into account(id,name,balance) values(“3”,“王五”,“0”); ;主鍵重複,插入失敗 |
|
T4 | select count(*) from account where id =3; 輸出0; |
|
T5 | rollback; |
select 某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,這個就有問題了。
很多人容易搞混不可重複讀和幻讀,確實這兩者有些相似。但不可重複讀重點在於update和delete,而幻讀的重點在於insert。
注意:不可重複讀和幻讀的區別是:前者是指讀到了已經提交的事務的更改資料(修改或刪除),後者是指讀到了其他已經提交事務的新增資料。
對於這兩種問題解決採用不同的辦法,防止讀到更改資料,只需對操作的資料新增行級鎖,防止操作中的資料發生變化;而防止讀到新增資料,往往需要新增表級鎖,將整張表鎖定,防止新增資料(oracle採用多版本資料的方式實現)。
當隔離級別設定為可序列化,強制事務序列執行,避免了前面說的幻讀的問題。
轉載自https://blog.csdn.net/Vincent2014Linux/article/details/89669762