innodb數(shù)據(jù)庫事務(wù)的隔離級(jí)別
讀未提交(Read Uncommitted)
同時(shí)開啟2個(gè)事務(wù),第一個(gè)事務(wù)里做的修改郑气,第二個(gè)事務(wù)立刻就能查詢到該變化例书。該隔離級(jí)別可能產(chǎn)生的最大問題是憾赁,當(dāng)?shù)谝粋€(gè)事務(wù)回滾時(shí)告喊,所以第二個(gè)事務(wù)之前讀取的數(shù)據(jù)變化是沒有發(fā)生的聪富。也就是我們常說的臟讀肝谭。
讀已提交(Read Committed)
這個(gè)隔離級(jí)別是很多其他數(shù)據(jù)庫默認(rèn)的隔離級(jí)別如oracle 纺铭。同時(shí)開啟2個(gè)事務(wù),第一個(gè)事務(wù)里做的修改搏恤,第二個(gè)事務(wù)無法查詢到該變化汗菜,但是如果第一個(gè)事務(wù)被提交,第二個(gè)事務(wù)立刻能獲取到該數(shù)據(jù)挑社。但是由于sql的執(zhí)行和數(shù)據(jù)庫事務(wù)的提交是有時(shí)間間隔的,所以還是可能發(fā)生幻讀的情況巡揍。第一個(gè)事務(wù)將表中所有數(shù)據(jù)的特定字段進(jìn)行修改痛阻,第二個(gè)事務(wù)向表中插入1條新的記錄,第二個(gè)事務(wù)提交腮敌。第一個(gè)事務(wù)提交阱当。在這種情況下第一個(gè)事務(wù)返回執(zhí)行成功俏扩,但是表中的仍然有1條記錄的數(shù)據(jù)未被修改,就好像發(fā)生了幻覺一樣弊添。而且如果在第一個(gè)事務(wù)中先查詢結(jié)果條數(shù)录淡,然后在執(zhí)行更新操作,而在這個(gè)2個(gè)操作之間第二個(gè)事務(wù)提交了1條記錄油坝,就會(huì)發(fā)生更新操作跟事先查詢確定的結(jié)果條數(shù)不一致的情況嫉戚。也想是發(fā)生了幻覺。
可重復(fù)讀(Repeatable Read)
該隔離級(jí)別是mysql默認(rèn)的事務(wù)隔離級(jí)別澈圈,在該隔離級(jí)別中彬檀。第一個(gè)事務(wù)能查詢到的數(shù)據(jù),是這個(gè)事務(wù)開啟時(shí)數(shù)據(jù)庫的一個(gè)快照狀態(tài)瞬女,所以即使第二個(gè)事務(wù)已經(jīng)進(jìn)行了數(shù)據(jù)提交窍帝,第一個(gè)事務(wù)依然不能發(fā)現(xiàn)該數(shù)據(jù)。就解決一個(gè)事務(wù)中多次查詢結(jié)果條數(shù)不一致的這個(gè)問題诽偷,這個(gè)問題是幻讀的一個(gè)場景坤学。而插入和刪除操作mysql 使用 Next-Key Lock 來解決幻讀問題。
串行化(Serializable)
在該隔離級(jí)別所有操作都是順序執(zhí)行报慕,不存在并發(fā)問題深浮,所以不存在 臟讀和幻讀。
臟讀場景
我使用的工具為DataGrip每個(gè)查詢窗口相當(dāng)于個(gè)一個(gè)連接卖子。設(shè)置事務(wù)的模式手動(dòng)提交略号,事務(wù)的隔離級(jí)別為讀未提交。
第一個(gè)窗口執(zhí)行語句
#確認(rèn)數(shù)據(jù)信息
select user_id,balance,now() as now from user where user_id =1
結(jié)果信息
1,430000,2022-05-12 03:10:56
第二個(gè)窗口執(zhí)行信息洋闽,注意當(dāng)前狀態(tài)未提交
update user set balance = balance+100 where user_id =1
第一窗口再次執(zhí)行結(jié)果變?yōu)椋?/p>
1,430100,2022-05-12 03:13:20
第二個(gè)窗口回滾事務(wù)玄柠。第一個(gè)窗口再次執(zhí)行結(jié)果變?yōu)?/p>
1,430000,2022-05-12 03:18:53
如果我們在實(shí)際業(yè)務(wù)中,第一個(gè)窗口在查詢到430100后就進(jìn)行也后續(xù)的業(yè)務(wù)處理诫舅,在第二個(gè)窗口信息回滾后羽利,將會(huì)產(chǎn)生業(yè)務(wù)損失。
幻讀場景
幻讀中的不可重復(fù)讀現(xiàn)象
首先我們先將事務(wù)的隔離級(jí)別改為讀以提交
第一個(gè)窗口執(zhí)行
select * from user
返回結(jié)果
1 430000 2022-05-07 01:02:05
2 2000 2022-05-07 01:05:03
3 2000 2022-05-07 01:06:47
第二個(gè)窗口執(zhí)行
insert into user(user_id,balance) values(4,100);
commit;
重新執(zhí)行第一個(gè)窗口語句
1 430000 2022-05-07 01:02:05
2 2000 2022-05-07 01:05:03
3 2000 2022-05-07 01:06:47
4 100 2022-05-12 03:28:01
在這情況下發(fā)現(xiàn)數(shù)據(jù)多了1條刊懈,產(chǎn)生了不可重復(fù)讀現(xiàn)象这弧,其實(shí)也可以定義為幻讀的一種情況。
幻讀中數(shù)據(jù)更新現(xiàn)象
第一個(gè)窗口執(zhí)行語句
update user set create_time = '1999-01-01 00:00:00' where user_id >0
第二個(gè)窗口執(zhí)行語句
insert into user(user_id,balance) values(5,100);
commit;
第一個(gè)窗口執(zhí)行語句
select * from user
第一個(gè)窗口結(jié)果
1 430000 1999-01-01 00:00:00
2 2000 1999-01-01 00:00:00
3 2000 1999-01-01 00:00:00
4 100 1999-01-01 00:00:00
5 100 2022-05-12 03:33:49
我們發(fā)現(xiàn)第五條記錄一個(gè)createtime 沒有被更新的記錄虚汛,這明顯不是我們想要的結(jié)果匾浪,就是幻讀的第二種場景。
可重復(fù)讀隔離級(jí)別幻讀測試
首先將數(shù)據(jù)庫隔離級(jí)別改為可重復(fù)讀
第一個(gè)窗口執(zhí)行語句
update user set create_time = '1999-01-01 00:00:00' where user_id >0
第二個(gè)窗口執(zhí)行語句
insert into user(user_id,balance) values(6,100);
commit;
在這你會(huì)發(fā)現(xiàn)你的語句無法提交成功卷哩;
因?yàn)閕nnodb 的可重復(fù)讀蛋辈,讀操作不加鎖,但是寫操作是會(huì)進(jìn)行鎖操作的你的執(zhí)行結(jié)果 最終會(huì)得到 Lock wait timeout exceeded; try restarting transaction
所以innodb 可重復(fù)讀 通過 next key lock 來避免了這種幻讀場景。
但是了解了mysql innodb 避免幻讀的實(shí)現(xiàn)方式后冷溶,我們在實(shí)際的過程中也是會(huì)發(fā)生意料之外的事渐白,因?yàn)樗峭ㄟ^寫操作來加鎖的,我們進(jìn)行下面的實(shí)驗(yàn)逞频;
第一個(gè)窗口執(zhí)行語句
select * from user
執(zhí)行結(jié)果
1 430000 1999-01-01 00:00:00
2 2000 1999-01-01 00:00:00
3 2000 1999-01-01 00:00:00
4 100 1999-01-01 00:00:00
5 100 1999-01-01 00:00:00
第二個(gè)窗口執(zhí)行語句
insert into user(user_id,balance) values(6,100);
commit;
第一個(gè)窗口執(zhí)行語句
select * from user
第一個(gè)窗口執(zhí)行結(jié)果
1 430000 1999-01-01 00:00:00
2 2000 1999-01-01 00:00:00
3 2000 1999-01-01 00:00:00
4 100 1999-01-01 00:00:00
5 100 1999-01-01 00:00:00
第一個(gè)窗口執(zhí)行語句
update user set create_time = '1999-01-01 00:00:00' where user_id >0
第一個(gè)窗口執(zhí)行語句
select * from user
第一個(gè)窗口執(zhí)行結(jié)果
1 430000 1999-01-01 00:00:00
2 2000 1999-01-01 00:00:00
3 2000 1999-01-01 00:00:00
4 100 1999-01-01 00:00:00
5 100 1999-01-01 00:00:00
6 100 1999-01-01 00:00:00
我們發(fā)現(xiàn)記錄6 纯衍,并且該記錄的創(chuàng)建時(shí)間已經(jīng)被更新為1999-01-01 00:00:00 這跟我們想想中的可重復(fù)讀和幻讀的解決發(fā)生了沖突。究其原因是因?yàn)槲覀兊诙€(gè)事務(wù)提交了苗胀。而且在第二個(gè)事務(wù)提交之前 我們第一個(gè)事務(wù)并沒有進(jìn)行寫操作襟诸,所以第一個(gè)事務(wù)對于該表自然也就沒有鎖。就會(huì)產(chǎn)生這種詭異的幻讀現(xiàn)象柒巫。那為什么6這個(gè)記錄為什么會(huì)被查出來呢励堡?因?yàn)閕nnodb 的重復(fù)讀使用的mvvc 多版本并發(fā)控制 ,這個(gè)6 是被這個(gè)事務(wù)更新的堡掏,所以他的版本號(hào)就是這個(gè)事務(wù)的版本號(hào)应结,所有就能看到這條記錄,所以并不是破壞了可重復(fù)讀泉唁,只是可重復(fù)讀的實(shí)現(xiàn)方式導(dǎo)致了這個(gè)結(jié)果鹅龄。
ps:如果你在開發(fā)中使用mybatis 你會(huì)發(fā)現(xiàn)其實(shí)無論數(shù)據(jù)庫的事務(wù)隔離級(jí)別是什么,你一個(gè)事務(wù)中多次查詢同一個(gè)語句你會(huì)得到相同的結(jié)果亭畜,因?yàn)閙ybatis默認(rèn)的一級(jí)緩存是基于sqlsession 的 所以只要你語句和參數(shù)沒有變化就會(huì)的到相同的結(jié)果扮休,所以在上述的場景中你將不會(huì)獲得記錄6 ,你的程序可能會(huì)產(chǎn)生意向不到的bug拴鸵。