怎樣選擇MySQL事務(wù)隔離級別?
我們回到一個(gè)經(jīng)常會(huì)討論的問題:MySQL事務(wù)隔離級別究竟應(yīng)該怎么選擇?
先說一下我自己的見解:
建議在RC和RR兩個(gè)隔離級別中選一種,如果能接受幻讀,需要并發(fā)高點(diǎn),就可以配置成RC:
如果不能接受幻讀的情況,就設(shè)置成RR隔離級別。
我們就來詳細(xì)介紹一下MySQL的4種事務(wù)隔離級別。
1 通過基本定義認(rèn)識事務(wù)隔離級別
四種隔離級別的基本定義(如果覺得文字不太好理解,可以結(jié)合文章后面的實(shí)驗(yàn)部分):
事務(wù)隔離級別 | 解釋 |
Read uncommitted (讀未提交,簡稱:RU) | 所有事務(wù)都可以看到其它未提交事務(wù)的執(zhí)行結(jié)果,這也就是臟讀。 |
Read Committed (讀已提交,簡稱:RC) | 一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變,某個(gè)事務(wù)執(zhí)行期間可能有其他事務(wù)提交,所以可能出現(xiàn)幻讀 |
Repeatable Read (可重復(fù)讀,簡稱:RR) | 這是MySQL的默認(rèn)事務(wù)隔離級別,它確保同一事務(wù)相同的語句多次查詢時(shí),會(huì)看到同樣的數(shù)據(jù)行。消除了臟讀、不可重復(fù)讀,默認(rèn)也不會(huì)出現(xiàn)幻讀 |
Serializable (串行) | 這是最高的隔離級別,它通過強(qiáng)制事務(wù)排序,使不同事務(wù)之間不可能相互沖突,從而解決幻讀問題 |
解釋一下幻讀:在一個(gè)事務(wù)里面,按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足查詢條件的新數(shù)據(jù)。這種情況就稱為幻讀。
2 通過實(shí)驗(yàn)認(rèn)識Read uncommitted
創(chuàng)建測試表和寫入測試數(shù)據(jù):
use martin;
drop procedure if exists insert_t21;
delimiter ;;
create procedure insert_t21()
begin
drop table if exists t21;
CREATE TABLE `t21` (
`id` int NOT NULL AUTO_INCREMENT,
`a` int NOT NULL,
`b` int NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_c` (`a`)
) ENGINE=InnoDB CHARSET=utf8mb4;
insert into t21(a,b) values (1,1),(2,2);
end;;
delimiter ;
按下圖進(jìn)行RU隔離級別的實(shí)驗(yàn):
步驟 | session1 | session2 |
1 | call insert_t21(); | |
2 | set session transaction_isolation='READ-UNCOMMITTED'; | set session transaction_isolation='READ-UNCOMMITTED'; |
3 | begin; | begin; |
4 | select * from t21 where a=1; | |
5 | insert into t21(a,b) values (1,3); | |
6 | select * from t21 where a=1; | |
7 | commit; | commit; |
上面的實(shí)驗(yàn)中,第 5 步中 session2 寫入了一條 a、b 值分別為 1、3 的記錄,在第 6 步中,session2 中的事務(wù)還沒提交,但是 session1 就能看到 session2 寫入的數(shù)據(jù),出現(xiàn)了臟讀現(xiàn)象。
3 通過實(shí)驗(yàn)認(rèn)識Read Committed
按下圖,進(jìn)行RC隔離級別的實(shí)驗(yàn):
ID | session1 | session2 |
1 | call insert_t21 (); | |
2 | set session transaction_isolation='READ-COMMITTED'; | set session transaction_isolation='READ-COMMITTED'; |
3 | begin; | begin; |
4 | select * from t21 where a=1; | |
5 | insert into t21(a,b) values (1,3); | |
6 | select * from t21 where a=1; | |
7 | commit; | |
8 | select * from t21 where a=1; | |
9 | commit; |
實(shí)驗(yàn)結(jié)果是:
session2 寫入了新數(shù)據(jù)未提交的情況下,session1 無法查看到新記錄,等到 session2 提交之后,session1 才能看到第 5 步 session2 寫入的數(shù)據(jù)。
但是存在一個(gè)問題就是在session1這個(gè)事務(wù)里面,按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù),卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù)。也就是出現(xiàn)了幻讀。
4 通過實(shí)驗(yàn)認(rèn)識Repeatable Read
再來看下RR隔離級別下的實(shí)驗(yàn):
ID | session1 | session2 |
1 | call insert_t21 (); | |
2 | set session transaction_isolation='REPEATABLE-READ'; | set session transaction_isolation='REPEATABLE-READ'; |
3 | begin; | begin; |
4 | select * from t21 where a=1; | |
5 | insert into t21(a,b) values (1,3); | |
6 | select * from t21 where a=1; | |
7 | commit; | |
8 | select * from t21 where a=1; | |
9 | commit; | |
10 | select * from t21 where a=1; |
實(shí)驗(yàn)結(jié)論:
session2 寫入了新數(shù)據(jù)未提交的情況下,session1 無法查看到新記錄,等到 session2 提交但是 session1 還未提交時(shí),session1 還是不能看到新記錄,需要等 session1 事務(wù)提交之后,才能查看到第 5 步 session2 寫入的新數(shù)據(jù)。
也就是RR隔離級別下,在同一個(gè)事務(wù)里面,前后兩條一樣的語句,讀取的數(shù)據(jù)是一樣的。
5 通過實(shí)驗(yàn)認(rèn)識Serializable
進(jìn)行如下實(shí)驗(yàn):
ID | session1 | session2 |
1 | call insert_t21 (); | |
2 | set session transaction_isolation='SERIALIZABLE'; | set session transaction_isolation='SERIALIZABLE'; |
3 | begin; | begin; |
4 | select * from t21 where a=1; | |
5 | insert into t21(a,b) values (1,3); (等待) | |
6 | select * from t21 where a=1; | |
7 | commit; | session1 提交后,第 5 步中的寫入操作執(zhí)行成功 |
8 | commit; | |
9 | select * from t21 where a=1; |
當(dāng) session1 中有事務(wù)查詢 a=1 這行記錄時(shí),在 session2 就不能插入 a=1 的記錄,進(jìn)入等待。必須等 session1 提交后,session2 才能執(zhí)行成功。也就是讓事務(wù)串行進(jìn)行。
6 Read uncommitted的例子
拿零售業(yè)務(wù)場景來講,在事務(wù)隔離級別 RU 下:
比如顧客 A 在超市買單時(shí),
當(dāng)收銀員掃完顧客 A 的支付碼后,因?yàn)榫W(wǎng)絡(luò)原因,一直等待著(也就是整個(gè)支付過程的事務(wù)還沒結(jié)束);
這時(shí)收銀員去后臺數(shù)據(jù)查詢,看到 A 的錢已經(jīng)進(jìn)入超市賬戶了,然后讓顧客 A 離開。
過了一會(huì),整個(gè)支付過程回滾了,才發(fā)現(xiàn) A 實(shí)際是支付失敗。
這樣超市豈不是很虧。
這就是 RU 隔離級別可能導(dǎo)致臟讀的情況。
7 Read Committed的例子
顧客A在超市購買了90元的東西。
收銀系統(tǒng)查詢到顧客A還剩100元,足夠扣款,
A 的老婆在家網(wǎng)購,花掉了A賬戶里的這100塊,
收銀系統(tǒng)在扣除A賬戶90元時(shí),就會(huì)出現(xiàn)報(bào)錯(cuò),
顧客A肯定郁悶,不是明明錢夠么?
這就是 RC 隔離級別下的幻讀現(xiàn)象。
8 Repeatable Read的例子
顧客A在超市購買了90元的東西。
當(dāng)收銀系統(tǒng)查詢到顧客A還剩100 元,足夠扣款,
這期間A 的老婆在家網(wǎng)購,能查詢到 A 的賬戶里還有 100 元,但是想要用 A 賬戶里的 100 塊,卻發(fā)現(xiàn)并不能使用這 100 元,
A最后的扣款步驟也能正常完成,最終順利完成了整個(gè)付款過程,
這就是可重復(fù)讀的現(xiàn)象。
9 Serializable的例子
顧客A在超市購買了90元的東西。
當(dāng)收銀系統(tǒng)查詢到顧客A還剩100元,足夠扣款,
此時(shí)A 的老婆在家網(wǎng)購,想查詢 A 賬戶里還有多少錢,卻發(fā)現(xiàn)無法查看到,必須要等到 A 整個(gè)付款完成,其老婆才能去查詢余額,
這就是串行導(dǎo)致的。
10 如何選擇合適的事務(wù)隔離級別
對于RU隔離級別,會(huì)導(dǎo)致臟讀,從性能上看,也不會(huì)比其它隔離級別好太多,因此生產(chǎn)環(huán)境不建議使用。
對于RC隔離級別,相比RU隔離級別,不會(huì)出現(xiàn)臟讀;但是會(huì)出現(xiàn)幻讀,一個(gè)事務(wù)中的兩次執(zhí)行同樣的查詢,可能得到不一樣的結(jié)果。
對于 RR 隔離級別,相比RC隔離級別,解決了部分幻讀,我們在鎖那一章也有詳細(xì)講解,但是相對于RC,鎖的范圍可能更大了。
對于 Serializable 隔離級別,因?yàn)樗鼜?qiáng)制事務(wù)串行執(zhí)行,會(huì)在讀取的每一行數(shù)據(jù)上都加鎖,因此可能會(huì)導(dǎo)致大量的超時(shí)和鎖爭用的問題。生成環(huán)境也不建議使用。
因此總的來說,建議在RC和RR兩個(gè)隔離級別中選一種,如果能接受幻讀,需要并發(fā)高點(diǎn),就可以配置成RC,如果不能接受幻讀的情況,就設(shè)置成RR隔離級別。