消失的 100 萬(wàn),炸了!
這是我的錢(qián)包,共有 100 萬(wàn)元。


事務(wù)有哪些特性?
原子性(Atomicity):一個(gè)事務(wù)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié),而且事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤,會(huì)被回滾到事務(wù)開(kāi)始前的狀態(tài),就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣;
一致性(Consistency):數(shù)據(jù)庫(kù)的完整性不會(huì)因?yàn)槭聞?wù)的執(zhí)行而受到破壞,比如表中有一個(gè)字段為姓名,它有唯一約束,也就是表中姓名不能重復(fù),如果一個(gè)事務(wù)對(duì)姓名字段進(jìn)行了修改,但是在事務(wù)提交后,表中的姓名變得非唯一性了,這就破壞了事務(wù)的一致性要求,這時(shí)數(shù)據(jù)庫(kù)就要撤銷該事務(wù),返回初始化的狀態(tài)。
隔離性(Isolation):數(shù)據(jù)庫(kù)允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫(xiě)和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。
持久性(Durability):事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。
原子性和持久性是通過(guò) redo log (重做日志)來(lái)保證的;
一致性是通過(guò) undo log(回滾日志) 來(lái)保證的;
隔離性是通過(guò) MVCC(多版本并發(fā)控制) 或鎖機(jī)制來(lái)保證的;
并行事務(wù)會(huì)引發(fā)什么問(wèn)題?
臟讀

不可重復(fù)讀

幻讀

事務(wù)的隔離級(jí)別有哪些?
臟讀:讀到其他事務(wù)未提交的數(shù)據(jù);
不可重復(fù)讀:前后讀取的數(shù)據(jù)不一致;
幻讀:前后讀取的記錄數(shù)量不一致。

讀未提交(read uncommitted),指一個(gè)事務(wù)還沒(méi)提交時(shí),它做的變更就能被其他事務(wù)看到;
讀提交(read committed),指一個(gè)事務(wù)提交之后,它做的變更才能被其他事務(wù)看到;
可重復(fù)讀(repeatable read),指一個(gè)事務(wù)執(zhí)行過(guò)程中看到的數(shù)據(jù),一直跟這個(gè)事務(wù)啟動(dòng)時(shí)看到的數(shù)據(jù)是一致的,MySQL InnoDB 引擎的默認(rèn)隔離級(jí)別;
串行化(serializable );會(huì)對(duì)記錄加上讀寫(xiě)鎖,在多個(gè)事務(wù)對(duì)這條記錄進(jìn)行讀寫(xiě)操作時(shí),如果發(fā)生了讀寫(xiě)沖突的時(shí)候,后訪問(wèn)的事務(wù)必須等前一個(gè)事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行;


在「讀未提交」隔離級(jí)別下,可能發(fā)生臟讀、不可重復(fù)讀和幻讀現(xiàn)象;
在「讀提交」隔離級(jí)別下,可能發(fā)生不可重復(fù)讀和幻讀現(xiàn)象,但是不可能發(fā)生臟讀現(xiàn)象;
在「可重復(fù)讀」隔離級(jí)別下,可能發(fā)生幻讀現(xiàn)象,但是不可能臟讀和不可重復(fù)讀現(xiàn)象;
在「串行化」隔離級(jí)別下,臟讀、不可重復(fù)讀和幻讀現(xiàn)象都不可能會(huì)發(fā)生。

在「讀未提交」隔離級(jí)別下,事務(wù) B 修改余額后,雖然沒(méi)有提交事務(wù),但是此時(shí)的余額已經(jīng)可以被事務(wù) A 看見(jiàn)了,于是事務(wù) A 中余額 V1 查詢的值是 200 萬(wàn),余額 V2、V3 自然也是 200 萬(wàn)了;
在「讀提交」隔離級(jí)別下,事務(wù) B 修改余額后,因?yàn)闆](méi)有提交事務(wù),所以事務(wù) A 中余額 V1 的值還是 100 萬(wàn),等事務(wù) B 提交完后,最新的余額數(shù)據(jù)才能被事務(wù) A 看見(jiàn),因此額 V2、V3 都是 200 萬(wàn);
在「可重復(fù)讀」隔離級(jí)別下,事務(wù) A 只能看見(jiàn)啟動(dòng)事務(wù)時(shí)的數(shù)據(jù),所以余額 V1、余額 V2 的值都是 100 萬(wàn),當(dāng)事務(wù) A 提交事務(wù)后,就能看見(jiàn)最新的余額數(shù)據(jù)了,所以余額 V3 的值是 200 萬(wàn);
在「串行化」隔離級(jí)別下,事務(wù) B 在執(zhí)行將余額 100 萬(wàn)修改為 200 萬(wàn)時(shí),由于此前事務(wù) A 執(zhí)行了讀操作,這樣就發(fā)生了讀寫(xiě)沖突,于是就會(huì)被鎖住,直到事務(wù) A 提交后,事務(wù) B 才可以繼續(xù)執(zhí)行,所以從 A 的角度看,余額 V1、V2 的值是 100 萬(wàn),余額 V3 的值是 200萬(wàn)。
對(duì)于「讀未提交」隔離級(jí)別的事務(wù)來(lái)說(shuō),因?yàn)榭梢宰x到未提交事務(wù)修改的數(shù)據(jù),所以直接讀取最新的數(shù)據(jù)就好了;
對(duì)于「串行化」隔離級(jí)別的事務(wù)來(lái)說(shuō),通過(guò)加讀寫(xiě)鎖的方式來(lái)避免并行訪問(wèn);
對(duì)于「讀提交」和「可重復(fù)讀」隔離級(jí)別的事務(wù)來(lái)說(shuō),它們是通過(guò) **Read View **來(lái)實(shí)現(xiàn)的,它們的區(qū)別在于創(chuàng)建 Read View 的時(shí)機(jī)不同,大家可以把 Read View 理解成一個(gè)數(shù)據(jù)快照,就像相機(jī)拍照那樣,定格某一時(shí)刻的風(fēng)景。「讀提交」隔離級(jí)別是在每個(gè)讀取數(shù)據(jù)前都生成一個(gè) Read View,而「可重復(fù)讀」隔離級(jí)別是啟動(dòng)事務(wù)時(shí)生成一個(gè) Read View,然后整個(gè)事務(wù)期間都在用這個(gè) Read View。
可重復(fù)讀隔離級(jí)別是如何實(shí)現(xiàn)的?
Read View 中四個(gè)字段作用;
聚族索引記錄中兩個(gè)跟事務(wù)有關(guān)的隱藏列;

m_ids :指的是創(chuàng)建 Read View 時(shí)當(dāng)前數(shù)據(jù)庫(kù)中活躍且未提交的事務(wù)的事務(wù) id 列表,注意是一個(gè)列表。
min_trx_id :指的是創(chuàng)建 Read View 時(shí)當(dāng)前數(shù)據(jù)庫(kù)中活躍且未提交的事務(wù)中最小事務(wù)的事務(wù) id,也就是 m_ids 的最小值。
max_trx_id :這個(gè)并不是 m_ids 的最大值,而是創(chuàng)建 Read View 時(shí)當(dāng)前數(shù)據(jù)庫(kù)中應(yīng)該給下一個(gè)事務(wù)的 id 值;
creator_trx_id :指的是創(chuàng)建該 Read View 的事務(wù)的事務(wù) id。

trx_id,當(dāng)一個(gè)事務(wù)對(duì)某條聚族索引記錄進(jìn)行改動(dòng)時(shí),就會(huì)把該事務(wù)的事務(wù) id 記錄在 trx_id 隱藏列里;
roll_pointer,每次對(duì)某條聚族索引記錄進(jìn)行改動(dòng)時(shí),都會(huì)把舊版本的記錄寫(xiě)入到 undo 日志中,然后這個(gè)隱藏列是個(gè)指針,指向每一個(gè)舊版本記錄,于是就可以通過(guò)它找到修改前的記錄。

在事務(wù) A 的 Read View 中,它的事務(wù) id 是 51,由于與事務(wù) B 同時(shí)啟動(dòng),所以此時(shí)活躍的事務(wù)的事務(wù) id 列表是 51 和 52,活躍的事務(wù) id 中最小的事務(wù) id 是事務(wù) A 本身,下一個(gè)事務(wù) id 應(yīng)該是 53。
在事務(wù) B 的 Read View 中,它的事務(wù) id 是 52,由于與事務(wù) A 同時(shí)啟動(dòng),所以此時(shí)活躍的事務(wù)的事務(wù) id 列表是 51 和 52,活躍的事務(wù) id 中最小的事務(wù) id 是事務(wù) A,下一個(gè)事務(wù) id 應(yīng)該是 53。

如果記錄的 trx_id 比該事務(wù)的 Read View 中的 creator_trx_id 要小,且不在 m_ids 列表里,這意味著這條記錄的事務(wù)早就在該事務(wù)前提交過(guò)了,所以該記錄對(duì)該事務(wù)可見(jiàn);
如果記錄的 trx_id 比該事務(wù)的 Read View 中的 creator_trx_id 要大,且在 m_ids 列表里,這意味著該事務(wù)讀到的是和自己同時(shí)啟動(dòng)的另外一個(gè)事務(wù)修改的數(shù)據(jù),這時(shí)就不應(yīng)該讀取這條記錄,而是沿著 undo log 鏈條往下找舊版本的記錄,直到找到 trx_id 等于或者小于該事務(wù) id 的第一條記錄。
讀提交隔離級(jí)別是如何實(shí)現(xiàn)的?


總結(jié)
「讀提交」隔離級(jí)別是在每個(gè) select 都會(huì)生成一個(gè)新的 Read View,也意味著,事務(wù)期間的多次讀取同一條數(shù)據(jù),前后兩次讀的數(shù)據(jù)可能會(huì)出現(xiàn)不一致,因?yàn)榭赡苓@期間另外一個(gè)事務(wù)修改了該記錄,并提交了事務(wù)。
「可重復(fù)讀」隔離級(jí)別是啟動(dòng)事務(wù)時(shí)生成一個(gè) Read View,然后整個(gè)事務(wù)期間都在用這個(gè) Read View,這樣就保證了在事務(wù)期間讀到的數(shù)據(jù)都是事務(wù)啟動(dòng)前的記錄。
