啥是 MySQL 事務(wù)隔離級(jí)別?
本文轉(zhuǎn)載自微信公眾號(hào)「SH的全棧筆記」,作者SH的全棧筆記。轉(zhuǎn)載本文請(qǐng)聯(lián)系SH的全棧筆記公眾號(hào)。
之前發(fā)過一篇文章,簡(jiǎn)單了解 MySQL 中相關(guān)的鎖,里面提到了,如果我們使用的 MySQL 存儲(chǔ)引擎為 InnoDB ,并且其事務(wù)隔離級(jí)別是 RR 可重復(fù)讀的話,是可以避免幻讀的。
但是沒想到,都 1202 年了都還有人杠,說 InnoDB 的 RR 隔離級(jí)別下會(huì)出現(xiàn)幻讀,只能依靠 gap 和 next-key 這兩個(gè)鎖來防止幻讀 ,最開始我還以為是他真的不知道這個(gè)點(diǎn),就跟他聊,最后聊下來發(fā)現(xiàn),發(fā)現(xiàn)是在鉆牛角尖。
這個(gè)在下面講到 可重復(fù)讀 的隔離級(jí)別時(shí)會(huì)講。
本來我覺得事務(wù)隔離級(jí)別這玩意兒太簡(jiǎn)單沒啥可講的,但是經(jīng)過了上面這件事,我打算詳細(xì)的把事務(wù)隔離給講講。接下來順便就把 InnoDB 所有的事務(wù)隔離級(jí)別給摟一遍。
ACID
在聊事務(wù)隔離級(jí)別之前,我們需要知道 ACID 模型。
ACID 模型
分別代表:
- Atomicity 原子性
- Consistency 一致性
- Isolation 隔離型
- Durability 持久性
原子性,代表 InnoDB 事務(wù)中,所有的操作要么全部成功,要么全部失敗,不會(huì)處于某個(gè)中間狀態(tài)。說的更通俗一點(diǎn),如果事務(wù) A 失敗,其所做的所有的更改應(yīng)該全部回滾。
一致性,主要是保護(hù)數(shù)據(jù)的一致性,防止由于數(shù)據(jù)庫的崩潰而導(dǎo)致的數(shù)據(jù)一致性問題。舉個(gè)例子,我們更新 MySQL 的數(shù)據(jù),更新的數(shù)據(jù)會(huì)先到 InnoDB 的 Buffer Pool 中,如果此時(shí) MySQL 所在的機(jī)器突然意外重啟了,如果 InnoDB 沒有崩潰恢復(fù)機(jī)制,之前更新的數(shù)據(jù)就會(huì)丟失,數(shù)據(jù)的一致性問題就出現(xiàn)了。
很多其他的博客寫的是事務(wù)開要始前后,數(shù)據(jù)的完整性沒有被破壞。我表示看了根本看不懂,太抽象了。
隔離性,主要是指事務(wù)之間的隔離,再具體一點(diǎn),就是我們本篇文章要討論的事務(wù)隔離級(jí)別了。
持久性,主要是指我們新增或者刪除了某些數(shù)據(jù),一旦成功,這些操作應(yīng)該需要被持久化到磁盤上去。
ACID 模型可以理解成數(shù)據(jù)庫的設(shè)計(jì)范式,主要關(guān)注點(diǎn)在數(shù)據(jù)數(shù)據(jù)、及其本身的可靠性。而 MySQL 中的 InnoDB 就完全遵守 ACID 模型,并且在存儲(chǔ)引擎層就支持?jǐn)?shù)據(jù)一致性的校驗(yàn)和崩潰恢復(fù)的機(jī)制。
而 ACID 中的隔離型,就是我們這篇文章中討論的重點(diǎn)。
事務(wù)隔離級(jí)別
有很多文章上來就直接介紹事務(wù)隔離級(jí)別的種類,這個(gè)種類啥意思,那個(gè)種類怎么用。但我認(rèn)為應(yīng)該先了解為什么需要事務(wù)隔離級(jí)別,以及事務(wù)隔離級(jí)別到底解決了什么問題,這才是關(guān)鍵。
我們知道 InnoDB 中同時(shí)會(huì)有多個(gè)事務(wù)對(duì)數(shù)據(jù)進(jìn)行操作,舉一些例子:
- 假如事務(wù)A需要查詢 id=1 的數(shù)據(jù),但是事務(wù)A查詢完畢之后,事務(wù)B對(duì) id=1 的數(shù)據(jù)做了更新,那此時(shí)事務(wù)A再次執(zhí)行查詢,應(yīng)該看到更新前的數(shù)據(jù)還是更新后的數(shù)據(jù)?
- 或者還是上面那個(gè)例子,事務(wù)A讀取了事務(wù)B的數(shù)據(jù),但是如果事務(wù)B進(jìn)行回滾了怎么辦?事務(wù)A的數(shù)據(jù)不就變成了臟數(shù)據(jù)?
- 又或者事務(wù)A讀取了 1-3點(diǎn) 的日程安排,有4條,但是事務(wù)A讀取完成后事務(wù)B又向 1-3 點(diǎn)這個(gè)時(shí)間段插入了一條新的安排,那么事務(wù)A如果再次讀取,應(yīng)該顯示4條日程安排還是5條?
以上的這些問題,就需要事務(wù)隔離級(jí)別來回答了。其實(shí)以上的三種情況分別對(duì)應(yīng)不可重復(fù)讀、臟讀和幻讀。InnoDB 通過事務(wù)隔離級(jí)別分別的解決了上面的問題。所有的事務(wù)隔離級(jí)別如下:
- READ UNCOMMITTED 讀未提交
- READ COMMITTED 讀已提交
- REPEATABLE READ 可重復(fù)讀
- SERIALIZABLE 串行化
InnoDB 默認(rèn)的事務(wù)隔離級(jí)別為 REPEATABLE READ 。
讀未提交
事務(wù)A讀取了事務(wù)B還未提交的數(shù)據(jù)
如果事務(wù)B此時(shí)出錯(cuò)了進(jìn)行了回滾,那么事務(wù)A讀取到的數(shù)據(jù)就成為了臟數(shù)據(jù),從而造成臟讀。
如果事務(wù)B又更新事務(wù)A讀取的數(shù)據(jù),那么事務(wù)A再次讀取,讀取到了事務(wù)B修改的結(jié)果,這造成了不可重復(fù)讀。
而如果事務(wù)B又新增了數(shù)據(jù),事務(wù)A再次讀取,會(huì)讀取到事務(wù)B新增的數(shù)據(jù),這造成了幻讀。
所以總結(jié)來說,在讀未提交這個(gè)隔離級(jí)別下,會(huì)造成以下的問題:
- 臟讀
- 不可重復(fù)讀
- 幻讀
讀已提交
事務(wù)A讀取了事務(wù)B已經(jīng)提交的數(shù)據(jù)
如果事務(wù)B更新了事務(wù)A讀取到的數(shù)據(jù),并且提交,那么當(dāng)事務(wù)A再次進(jìn)行讀取,就會(huì)讀取到其他事務(wù)的變更,就造成了不可重復(fù)讀。
同理,如果事務(wù)B新增了數(shù)據(jù)并且提交,事務(wù)A再次進(jìn)行讀取時(shí)拿到了事務(wù)B剛剛提交的數(shù)據(jù),這就造成了幻讀。
所以總結(jié)來說,在讀已提交的隔離級(jí)別下,會(huì)造成:
- 不可重復(fù)讀
- 幻讀
可重復(fù)讀
事務(wù)A不會(huì)讀取到事務(wù)B更新的數(shù)據(jù),也不會(huì)讀到事務(wù)B新增的數(shù)據(jù)
在可重復(fù)讀場(chǎng)景下,不會(huì)出現(xiàn)臟讀、不會(huì)出現(xiàn)不可重復(fù)讀,可能會(huì)出現(xiàn)幻讀。
無論事務(wù)B做了什么操作,事務(wù)A查詢到的 id=1 的數(shù)據(jù)都是張三。
但是,在某些情況下,還是可能會(huì)出現(xiàn) 幻讀??芍貜?fù)讀 只是在某些情況下會(huì)產(chǎn)生幻讀,但絕對(duì)不是 InnoDB 無法避免幻讀。首先,InnoDB 在 RR 隔離級(jí)別下有很明確的解決幻讀的方式,那就是——臨鍵鎖,一種組合了 gap 鎖和記錄鎖的鎖。
接下來舉個(gè)例子來看在 RR 隔離級(jí)別下,什么情況會(huì)出現(xiàn)幻讀,什么情況下不會(huì)出現(xiàn)幻讀。首先是 可能會(huì)出現(xiàn)幻讀。
- SELECT * FROM `student` WHERE `id` > 1
由于 InnoDB 有 MVCC 來進(jìn)行多事務(wù)的并發(fā),此時(shí) SELECT 走的是快照讀,不會(huì)加鎖,那么臨鍵鎖就無法發(fā)揮其作用,如果有其他事務(wù)插入了一條數(shù)據(jù),那么事務(wù)再次執(zhí)行上面的語句是有可能會(huì)查出 id > 1 的數(shù)據(jù)。
但是如果顯示的進(jìn)行加鎖,則可以避免這個(gè)問題。
- SELECT * FROM `student` WHERE `id` > 1 FOR UPDATE
至于為什么臨鍵鎖可以避免幻讀,之前的文章已經(jīng)聊的很清楚,就不在此贅述了。
串行化
所以事務(wù)被強(qiáng)制的串行執(zhí)行
這樣從根本上就避免了并發(fā)的問題,但是這樣會(huì)使得 MySQL 的性能下降。因?yàn)楝F(xiàn)在同一時(shí)間只能有一個(gè)事務(wù)在運(yùn)行。
EOF
關(guān)于事務(wù)隔離級(jí)別就先介紹到這,之后有時(shí)間了就把 事務(wù)隔離級(jí)別 的底層原理給摟一遍。