您好,登錄后才能下訂單哦!
本文主要給大家簡單講講MySql事務(wù)隔離級別是什么及作用,相關(guān)專業(yè)術(shù)語大家可以上網(wǎng)查查或者找一些相關(guān)書籍補充一下,這里就不涉獵了,我們就直奔主題吧,希望MySql事務(wù)隔離級別是什么及作用這篇文章可以給大家?guī)硪恍嶋H幫助。
一、事務(wù)的四大特性(ACID)
了解事務(wù)隔離級別之前不得不了解的事務(wù)的四大特性。
1、原子性(Atomicity)
事務(wù)開始后所有操作,要么全部做完,要么全部不做。事務(wù)是一個不可分割的整體。事務(wù)在執(zhí)行過程中出錯,會回滾到事務(wù)開始之前的狀態(tài),以此來保證事務(wù)的完整性。類似于原子在物理上的解釋:指化學(xué)反應(yīng)不可再分的基本微粒,原子在化學(xué)反應(yīng)中不可分割 。
2、一致性(Consistency)
事務(wù)在開始和結(jié)束后,能保證數(shù)據(jù)庫完整性約束的正確性即數(shù)據(jù)的完整性。比如經(jīng)典的轉(zhuǎn)賬案例,A向B轉(zhuǎn)賬,我們必須保證A扣了錢,B一定能收到錢。個人理解類似于物理上的能量守恒。
3、隔離性(Isolation)
事務(wù)之間的完全隔離。比如A向一張銀行卡轉(zhuǎn)賬,避免在同一時間過多的操作導(dǎo)致賬戶金額的缺損,所以在A轉(zhuǎn)入結(jié)束之前是不允許其他針對此卡的操作的。
4、持久性(Durability)
事務(wù)的對數(shù)據(jù)的影響是永久性的。通俗的解釋為事務(wù)完成后,對數(shù)據(jù)的操作都要進行落盤(持久化)。事務(wù)一旦完成就是不可逆的,在數(shù)據(jù)庫的操作上表現(xiàn)為事務(wù)一旦完成就是無法回滾的。
二、事務(wù)并發(fā)問題
在互聯(lián)網(wǎng)的大潮中,程序存在的價值早已不是在傳統(tǒng)行業(yè)中為了幫人們解決一些復(fù)雜的業(yè)務(wù)邏輯。用戶體驗至上的互聯(lián)網(wǎng)時代,代碼就像西二旗地鐵站碼農(nóng)的腳步一樣,速度、速度、還是速度。當(dāng)然也不能坐錯了方向,本來想去西直門最后到了東直門(暫且理解為正確性吧)。相對于傳統(tǒng)行業(yè)復(fù)雜的業(yè)務(wù)邏輯,互聯(lián)網(wǎng)更注重并發(fā)帶給程序的速度與激情。當(dāng)然超速也是有代價的。在并發(fā)事務(wù)中,一不小心可憐的碼農(nóng)就要跑路了。
1、臟讀
又稱無效數(shù)據(jù)讀出。一個事務(wù)讀取另外一個事務(wù)還沒有提交的數(shù)據(jù)叫臟讀。
例如:事務(wù)T1修改了一行數(shù)據(jù),但是還沒有提交,這時候事務(wù)T2讀取了被事務(wù)T1修改后的數(shù)據(jù),之后事務(wù)T1因為某種原因Rollback了,那么事務(wù)T2讀取的就是臟數(shù)據(jù)。
2、不可重復(fù)讀
同一個事務(wù)中,多次讀出的同一數(shù)據(jù)是不一致的。
例如:事務(wù)T1讀取某一數(shù)據(jù),事務(wù)T2讀取并修改了該數(shù)據(jù),T1為了對讀取值進行檢驗而再次讀取該數(shù)據(jù),便得到了不同的結(jié)果。
3、幻讀
不好表述直接上例子吧:
在倉庫管理中,管理員要給剛到的一批商品進入庫管理,當(dāng)然入庫之前肯定是要查一下之前有沒有入庫記錄,確保正確性。管理員A確保庫中不存在該商品之后給該商品進行入庫操作,假如這時管理員B因為手快將已將該商品進行了入庫操作。這時管理員A發(fā)現(xiàn)該商品已經(jīng)在庫中。就像剛剛發(fā)生了幻讀一樣,本來不存在的東西,突然之間他就有了。
注:三種問題看似不太好理解,臟讀側(cè)重的是數(shù)據(jù)的正確性。不可重復(fù)度側(cè)重的于對數(shù)據(jù)的修改,幻讀側(cè)重于數(shù)據(jù)的新增和刪除。
三、MySql四種事務(wù)隔離級別
上一章節(jié)了解了高并發(fā)下對事務(wù)的影響。事務(wù)的四種隔離級別就是對以上三種問題的解決方案。
隔離級別 | 臟讀 | 不可重復(fù)度 | 幻讀 |
讀未提交(read-uncommitted) | 是 | 是 | 是 |
不可重復(fù)讀(read-committed) | 否 | 是 | 是 |
可重復(fù)讀(repeatable-read) | 否 | 否 | 是 |
可串行化(serializable) | 否 | 否 | 否 |
四、sql演示四種隔離級別
mysql版本:5.6
存儲引擎:InnoDB
工具:navicat
建表語句:
CREATE TABLE `tb_bank` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(16) COLLATE utf8_bin DEFAULT NULL, `account` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (1, '小明', 1000);
1、通過sql演示------read-uncommitted的臟讀
(2)read-uncommit導(dǎo)致的臟讀
所謂臟讀就是說,兩個事務(wù),其中一個事務(wù)能讀取到另一個事務(wù)未提交的數(shù)據(jù)。
場景:session1要轉(zhuǎn)出200元,session2轉(zhuǎn)入100元?;鶖?shù)為1000。順利完成正確的結(jié)果應(yīng)該是900元。但是我們假設(shè)session2轉(zhuǎn)入因為某種原因事務(wù)回滾。這時正確的結(jié)果應(yīng)該是800元。
演示步驟:
① 新建兩個session(會話,在navicat中表現(xiàn)為兩個查詢窗口,在mysql命令行中也是兩個窗口),分別執(zhí)行
select @@tx_isolation;//查詢當(dāng)前事務(wù)隔離級別 set session transaction isolation level read uncommitted;//將事務(wù)隔離級別設(shè)置為 讀未提交
② 兩個session都開啟事務(wù)
start transaction;//開啟事務(wù)
③ session1和session2:證明兩個操作執(zhí)行前賬戶余額為1000
select * from tb_bank where id=1;//查詢結(jié)果為1000
④ session2:此時假設(shè)session2的更新先執(zhí)行。
update tb_bank set account = account + 100 where id=1;
⑤ session1:在session2 commit之前session1開始執(zhí)行。
select * from tb_bank where id=1;//查詢結(jié)果:1100
⑥ session2:因為某種原因,轉(zhuǎn)入失敗,事務(wù)回滾。
rollback;//事務(wù)回滾 commit;//提交事務(wù)
⑦ 這時session1開始轉(zhuǎn)出,并且session1覺得⑤中查詢結(jié)果1100就是正確的數(shù)據(jù)。
update tb_bank set account=1100-200 where id=1; commit;
⑧ session1 和 session2查詢結(jié)果
select * from tb_bank where id=1;//查詢結(jié)果:900
這時我們發(fā)現(xiàn)因為session1的臟讀造成了最終數(shù)據(jù)不一致。正確的結(jié)果應(yīng)該為800;
到此我們怎么避免臟讀呢,將事務(wù)的隔離性增加一個級別到read-commit
(2)read-commit解決臟讀
重置數(shù)據(jù),使數(shù)據(jù)恢復(fù)到account=1000
① 新建兩個session,分別設(shè)置
set session transaction isolation level read committed;//將隔離級別設(shè)置為 不可重復(fù)讀
重復(fù)執(zhí)行(1)中的②③④步
⑤ session1執(zhí)行查詢
select * from tb_bank where id=1;//查詢結(jié)果為1000,這說明 不可重復(fù)讀 隔離級別有效的隔離了兩個會話的事務(wù)。
這時我們發(fā)現(xiàn),將事務(wù)的隔離升級為read-committed;后有效的隔離了兩個事務(wù),使得session1中的事務(wù)無法查詢到session2中事務(wù)對數(shù)據(jù)的改動。有效的避免了臟讀。
2、通過sql演示-----read-committed的不可重復(fù)讀
(1)read-commit的不可重復(fù)讀
重置數(shù)據(jù),使數(shù)據(jù)恢復(fù)到account=1000
所謂的不可重復(fù)讀就是說,一個事務(wù)不能讀取到另一個未提交的事務(wù)的數(shù)據(jù),但是可以讀取到提交后的數(shù)據(jù)。這個時候就造成了兩次讀取的結(jié)果不一致了。所以說是不可重復(fù)讀。
READ COMMITTED 隔離級別下,每次讀取都會重新生成一個快照,所以每次快照都是最新的,也因此事務(wù)中每次SELECT也可以看到其它已commit事務(wù)所作的更改
場景:session1進行賬戶的查詢,session2進行賬戶的轉(zhuǎn)入100。
session1開啟事務(wù)準(zhǔn)備對賬戶進行查詢?nèi)缓蟾?,這時session2也對該賬戶開啟了事務(wù)進行更新。正確的結(jié)果應(yīng)該是在session1開啟事務(wù)以后查詢讀到的結(jié)果應(yīng)該是一樣的。
① 新建兩個session,分別設(shè)置
set session transaction isolation level read committed;
② session1和session2分別開啟事務(wù)
start transaction;
③ session1第一次查詢:
select * from tb_bank where id=1;//查詢結(jié)果:1000
④ session2進行更新:
update tb_bank set account = account+100 where id=1; select * from tb_bank where id=1;//查詢結(jié)果:1100
⑤ session1第二次查詢:
select * from tb_bank where id=1;//查詢結(jié)果:1100。和③中查詢結(jié)果對比,session1兩次查詢結(jié)果不一致。
查看查詢結(jié)果可知,session1在開啟事務(wù)期間發(fā)生重復(fù)讀結(jié)果不一致,所以可以看到read commit事務(wù)隔離級別是不可重復(fù)讀的。顯然這種結(jié)果不是我們想要的。
重置數(shù)據(jù),使數(shù)據(jù)恢復(fù)到account=1000
① 新建兩個session,分別設(shè)置
set session transaction isolation level repeatable read;
重復(fù)(1)中的②③④
⑤ session1第二次查詢:
select * from tb_bank where id=1;//查詢結(jié)果為:1000
從結(jié)果可知,repeatable-read的隔離級別下,多次讀取結(jié)果是不受其他事務(wù)影響的。是可重復(fù)讀的。到這里產(chǎn)生了一個疑問,那session1在讀到的結(jié)果中依然是session2更新前的結(jié)果,那session1中繼續(xù)轉(zhuǎn)入100能得到正確的1200的結(jié)果嗎?
繼續(xù)操作:
⑥ session1轉(zhuǎn)入100:
update tb_bank set account=account+100 where id=1;
到這里感覺自己被騙了,鎖,鎖,鎖。session1的更新語句被阻塞了。只有session2中的update語句commit之后,session1中才能繼續(xù)執(zhí)行。session的執(zhí)行結(jié)果是1200,這時發(fā)現(xiàn)session1并不是用1000+100計算的,因為可重復(fù)讀的隔離級別下使用了MVCC機制,select操作不會更新版本號,是快照讀(歷史版本)。insert、update和delete會更新版本號,是當(dāng)前讀(當(dāng)前版本)。
3、通過sql演示-----repeatable-read的幻讀
在業(yè)務(wù)邏輯中,通常我們先獲取數(shù)據(jù)庫中的數(shù)據(jù),然后在業(yè)務(wù)中判斷該條件是否符合自己的業(yè)務(wù)邏輯,如果是的話,那么就可以插入一部分?jǐn)?shù)據(jù)。但是mysql的快照讀可能在這個過程中會產(chǎn)生意想不到的結(jié)果。
場景模擬:
session1開啟事務(wù),先查詢有沒有小張的賬戶信息,沒有的話就插入一條。這是session2也執(zhí)行和session1同樣的操作。
準(zhǔn)備工作:插入兩條數(shù)據(jù)
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (2, '小紅', 800); INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (3, '小磊', 6000);
(1)repeatable-read的幻讀
① 新建兩個session都執(zhí)行
set session transaction isolation level repeatable read; start transaction; select * from tb_bank;//查詢結(jié)果:(這一步很重要,直接決定了快照生成的時間)
結(jié)果都是:
② session2插入數(shù)據(jù)
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小張', 8000); select * from tb_bank;
結(jié)果數(shù)據(jù)插入成功。此時session2提交事務(wù)
commit;
③ session1進行插入
插入之前我們先看一下當(dāng)前session1是否有id=4的數(shù)據(jù)
select * from tb_bank;
結(jié)果session1中沒有該條記錄,這時按照我們通常的業(yè)務(wù)邏輯,此時應(yīng)該是能成功插入id=4的數(shù)據(jù)。繼續(xù)執(zhí)行:
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小張', 8000);
結(jié)果插入失敗,提示該條已經(jīng)存在,但是我們查詢里面并沒有這一條數(shù)據(jù)啊。為什么會插入失敗呢?
因為①中的select語句生成了快照,之后的讀操作(未加讀鎖)都是進行的快照讀,即在當(dāng)前事務(wù)結(jié)束前,所有的讀操作的結(jié)果都是第一次快照讀產(chǎn)生的快照版本。疑問又來了,為什么②步驟中的select語句讀到的不是快照版本呢?因為update語句會更新當(dāng)前事務(wù)的快照版本。具體參閱第五章節(jié)。
重復(fù)(1)中的①②
③ session1進行插入
插入之前我們先看一下當(dāng)前session1是否有id=4的數(shù)據(jù)
select * from tb_bank;
結(jié)果session1中沒有該條記錄,這時按照我們通常的業(yè)務(wù)邏輯,此時應(yīng)該是能成功插入id=4的數(shù)據(jù)。
select * from tb_bank lock in share mode;//采用當(dāng)前讀
結(jié)果:發(fā)現(xiàn)當(dāng)前結(jié)果中已經(jīng)有小張的賬戶信息了,按照業(yè)務(wù)邏輯,我們就不在繼續(xù)執(zhí)行插入操作了。
這時我們發(fā)現(xiàn)用當(dāng)前讀避免了repeatable-read隔離級別下的幻讀現(xiàn)象。
4、serializable隔離級別
在此級別下我們就不再做serializable的避免幻讀的sql演示了,畢竟是給整張表都加鎖的。
五、當(dāng)前讀和快照讀
本想把當(dāng)前讀和快照讀單開一片博客,但是為了把幻讀總結(jié)明白,暫且在本章節(jié)先簡單解釋下快照讀和當(dāng)前讀。后期再追加一篇MVCC,next-key的博客吧。。。
1、快照讀:即一致非鎖定讀。
① InnoDB存儲引擎下,查詢語句默認(rèn)執(zhí)行快照讀。
② RR隔離級別下一個事務(wù)中的第一次讀操作會產(chǎn)生數(shù)據(jù)的快照。
③ update,insert,delete操作會更新快照。
四種事務(wù)隔離級別下的快照讀區(qū)別:
① read-uncommitted和read-committed級別:每次讀都會產(chǎn)生一個新的快照,每次讀取的都是最新的,因此RC級別下select結(jié)果能看到其他事務(wù)對當(dāng)前數(shù)據(jù)的修改,RU級別甚至能讀取到其他未提交事務(wù)的數(shù)據(jù)。也因此這兩個級別下數(shù)據(jù)是不可重復(fù)讀的。
② repeatable-read級別:基于MVCC的并發(fā)控制,并發(fā)性能極高。第一次讀會產(chǎn)生讀數(shù)據(jù)快照,之后在當(dāng)前事務(wù)中未發(fā)生快照更新的情況下,讀操作都會和第一次讀結(jié)果保持一致??煺债a(chǎn)生于事務(wù)中,不同事務(wù)中的快照是完全隔離的。
③ serializable級別:從MVCC并發(fā)控制退化為基于鎖的并發(fā)控制。不區(qū)別快照讀與當(dāng)前讀,所有的讀操作均為當(dāng)前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。Serializable隔離級別下,讀寫沖突,因此并發(fā)度急劇下降。(鎖表,不建議使用)
如何產(chǎn)生當(dāng)前讀
① select ... lock in share mode
② select ... for update
③ update,insert,delete操作都是當(dāng)前讀。
讀取之后,還需要保證當(dāng)前記錄不能被其他并發(fā)事務(wù)修改,需要對當(dāng)前記錄加鎖。①中對讀取記錄加S鎖 (共享鎖),②③X鎖 (排它鎖)。
① update,insert,delete操作為什么都是當(dāng)前讀?
簡單來說,不執(zhí)行當(dāng)前讀,數(shù)據(jù)的完整性約束就有可能遭到破壞。尤其在高并發(fā)的環(huán)境下。
分析update語句的執(zhí)行步驟:update table set ... where ...;
InnoDB引擎首先進行where的查詢,查詢到的結(jié)果集從第一條開始執(zhí)行當(dāng)前讀,然后執(zhí)行update操作,然后當(dāng)前讀第二條數(shù)據(jù),執(zhí)行update操作......所以每次執(zhí)行update都伴隨著當(dāng)前讀。delete也是一樣,畢竟要先查到該數(shù)據(jù)才能刪除。insert有點不同,insert操作執(zhí)行前需要執(zhí)行唯一鍵的檢查?!鞠嚓P(guān)推薦:MySQL教程】
MySql事務(wù)隔離級別是什么及作用就先給大家講到這里,對于其它相關(guān)問題大家想要了解的可以持續(xù)關(guān)注我們的行業(yè)資訊。我們的板塊內(nèi)容每天都會捕捉一些行業(yè)新聞及專業(yè)知識分享給大家的。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。