深挖MySQL——事務(五)事務的隔離級別
在聊事務的隔離級別前,我們先看看事務之間若是沒有隔離性,會產生什麼問題。總的來說,我們將它總結為三種情況:髒讀(Dirty reads)、不可重複讀(Non-repeatable reads)、幻讀(Phantom reads)。下面來看看它們發生的場景以及導致的後果:
髒讀(Dirty reads):
事務A讀取到了事務B還沒有提交的資料,並在此基礎上進行操作。如果B事務rollback,那麼A事務所讀取到的資料就是不正確的,會帶來問題。
不可重複讀(Non-repeatable reads):
在同一事務範圍內讀取兩次相同的資料,所返回的結果不同。比如事務B第一次讀資料後,事務A更新資料並commit,那麼事務B第二次讀取的資料就與第一次是不一樣的。
幻讀(Phantom reads):
事務A讀取到了另一個事務B新提交的資料,不同於不可重複讀的是,幻讀發生在事務B提交Insert操作的場景。比如說,事務A對一個表中所有行的資料按照某規則進行修改(整表操作),同時,事務B向表中插入了一行原始資料,那麼後面事務A再對錶進行操作時,會發現表中居然還有一行資料沒有被修改,就像發生了幻覺一樣。
為了解決以上問題,事務的隔離性I不可或缺,由於時空成本的問題,事務又引入了隔離級別的概念,相對的隔離級別程度越高越安全,但是效率越低。事務的隔離級別劃分為:
Read uncommitted讀未提交:
最低級別,以上情況均無法保證,僅僅保證事務的原子性和永續性。
Read committed讀已提交:
可避免髒讀情況發生,oracle預設事務隔離級別。
Repeatable read可重複度:
字面意思,可以避免髒讀和不可重複度,在Innodb的實現中,由於區間鎖的緣故,也可以避免幻讀,Innodb將它設定為預設隔離級別。
Serializable序列化:
可避免髒讀、不可重複讀、幻讀情況的發生。相當於Java的重量級鎖,效率極低。
事務的四個隔離級別只是標準SQL規範中的定義,具體的實現由各個資料庫自行提供。因此實現的程度也是各不相同。像Innodb的repeatable read級別以及基本上實現了serializable的效果。下面我們來看看Innodb是付出了什麼代價,如何實現這四個隔離級別的。
Read uncommitted
隔離級別“讀未提交”作為最低級別,沒有避免任何會發生的壞情況,因此Innodb沒有付出任何代價,僅僅是由redo log和undo log來保證事務的原子性A、永續性D。當只有一個事務的情況的時候也能保證事務的一致性C:當有兩個事務以上,事務A操作使用者1給使用者2轉賬,此時使用者2的資料正在被另一個事務B操作,此時無法保證事務的一致性:
事務A
事務B
begin;
begin;
使用者1餘額扣款100元
使用者2餘額資訊某修改操作
使用者2餘額增加100元
……
commit;
由於某種操作導致事務回滾
rollback;
可以看到此時使用者1的餘額已被扣除,而使用者2的餘額將處於一個錯誤的狀態。事務的一致性遭到破壞,因此隔離級別讀未提交只推薦用於實時性要求不高的只讀操作。
Read committed
隔離級別讀已提交會將update操作的寫鎖保留到事務提交後才釋放,避免了髒讀的出現。同時由於上篇提到的Innodb在讀已提交隔離級別下雖然使用了快照讀(快照讀原理請看上篇),但是Read View在每次快照讀的時候才生成,因此讀已提交級別是無法避免不可重複讀問題的。
Repeatable read
隔離級別可重複讀相對讀已提交級別,添加了區間鎖的代價(區間鎖原理請看上篇),並且由於使用了快照讀,並且Read View生成在事務第一次執行快照讀的時間點,因此可以避免不可重複讀和幻讀的問題。但是需要注意的是MVCC是有失效的場景的,舉例如下:
1、新建表demo01:
CREATE TABLE `demo01` (`id` INT(11) NOT NULL AUTO_INCREMENT,`name` VARCHAR(50) NOT NULL,`age` INT(11) NOT NULL,PRIMARY KEY (`id`),INDEX `name` (`name`))
2、插入測試資料
3、開始事務A和事務B操作資料
事務A
事務B
begin;
begin;
select * from demo01 where id=3;
結果集:{id:3,name:張三3,age:3}
select * from demo01 where id=3;
結果集:{id:3,name:張三3,age:3}
update demo01 set name=‘zhangsan3’ where id=3;
commit;
select * from demo01 where id=3; // 快照讀
結果集:{id:3,name:張三3,age:3}
update demo01 set age=30 where id=3;
select * from demo01 where id=3; // 當前讀
結果集:{id:3,name:zhangsan3,age:30}
已經搭建了環境的小夥伴可以操作試試,我們可以發現,當事務A對資料執行了update操作後,下一次對資料select就不是使用快照讀了,而是會把當前資料讀出,這使的靠MVCC實現的避免不可重複讀和避免幻讀的效果沒有了。當然,由於區間鎖的存在,Repeatable Read級別仍舊可以保證對幻讀的避免(原理請看上一篇)。
Serializable
序列化級別不必多說,絕對可以避免以上一切問題,原因是不管使讀寫資料,都會加鎖,並將鎖保留到事務結束以後。但是可想而知,併發的情況下序列化的隔離級別效率低的可怕,不推薦使用。
如有錯誤,敬請斧正;歡迎轉載,但請務必註明出處;最後,在此向神奇的海螺保證,
絕不太監
!!!