深挖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、插入測試資料

深挖MySQL——事務(五)事務的隔離級別

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

序列化級別不必多說,絕對可以避免以上一切問題,原因是不管使讀寫資料,都會加鎖,並將鎖保留到事務結束以後。但是可想而知,併發的情況下序列化的隔離級別效率低的可怕,不推薦使用。

如有錯誤,敬請斧正;歡迎轉載,但請務必註明出處;最後,在此向神奇的海螺保證,

絕不太監

!!!