1. 程式人生 > 實用技巧 >快速理解髒讀,不可重複讀,幻讀

快速理解髒讀,不可重複讀,幻讀

轉載: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

,輸出都是REPEATABLE-READ,表明MySQL預設隔離級別為REPEATABLE-READ(可重複)讀。

*髒讀

表中的資料如下,設定隔離級別為未提交讀

時間客戶端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