淺析MySQL中的事務隔離級別,聊聊其實現原理

青灯夜游
發布: 2022-03-08 10:21:54
轉載
2193 人瀏覽過

這篇文章帶大家了解下MySQL中的事務,聊聊事務隔離性的實作原理,希望能夠給大家幫忙!

淺析MySQL中的事務隔離級別,聊聊其實現原理

說到資料庫事務,大家腦子裡一定很容易蹦出一堆事務的相關知識,如事務的ACID特性,隔離級別,解決的問題(臟讀,不可重複讀,幻讀)等等,但是可能很少有人真正的清楚事務的這些特性又是怎麼實現的,為什麼要有四個隔離級別。

今天我們就先來聊聊MySQL中交易的隔離性的實作原理,後續還會繼續出文章分析其他特性的實作原理。

當然MySQL博大精深,文章疏漏之處在所難免,歡迎批評指正。

說明

MySQL的事務實作邏輯是位於引擎層的,且不是所有的引擎都支援事務的,以下的說明都是以InnoDB引擎為基準。

定義

隔離性(isolation)指的是不同事務先後提交並執行後,最終呈現出來的效果是串行的,也就是說,對於事務來說,它在執行過程中,感知到的資料變化應該只有自己操作所引起的,不存在其他交易引發的資料變化。

隔離性解決的是並發交易出現的問題

標準SQL隔離等級

隔離性最簡單的實作方式就是各個事務都串列執行了,如果前面的事務還沒執行完畢,後面的交易就都等待。但是這樣的實作方式很明顯並發效率不高,並不適合在實際環境中使用。

為了解決上述問題,實現不同程度的並發控制,SQL的標準制定者提出了不同的隔離等級:未提交讀取(read uncommitted)、提交讀取(read committed)、可重複讀取(repeatable read)、序列化讀取(serializable)。其中最高級隔離等級就是序列化讀,而在其他隔離等級中,由於交易是並發執行的,所以或多或少允許出現一些問題。請參閱以下的矩陣表:

# #未提交讀                                                                  提交讀                                   -                                #可重複閱讀                   -                        
隔離等級(: 允許出現,-:不允許出現) #髒讀 不可重複讀取 幻讀
############################ ##序列化讀                                    ######-        #############

注意,MySQL的InnoDB引擎在可重複讀取層級透過間隙鎖定解決了幻讀問題,透過MVCC解決了不可重複讀取的問題,具體見下面的分析

實作原理

標準SQL交易隔離層級實作原理

我們上面遇到的問題其實就是並發交易下的控制問題,解決並發交易最常見的方式就是悲觀並發控制了(也就是資料庫中的鎖)。標準SQL事務隔離等級的實現是依賴鎖定的,我們來看下具體是怎麼實現的:

未提交讀取(RU)提交讀取(RC)   行級共享鎖定(當閱讀時才加鎖定)可重複讀取(RR)行層級共享鎖定序列化讀取(S)  表級共享鎖定

可以看到,在只使用鎖來實現隔離級別的控制的時候,需要頻繁的加鎖解鎖,而且很容易發生讀寫的衝突(例如在RC級別下,事務A更新了數據行1,事務B則在事務A提交前讀取資料行1都要等待事務A提交並釋放鎖定)。

為了不加鎖解決讀取和寫入衝突的問題,MySQL引入了MVCC機制,詳細可見我先前的分析文章:一文讀懂資料庫中的樂觀鎖定和悲觀鎖定和MVCC。

InnoDB交易隔離層級實作原則

在往下分析之前,我們有幾個概念需要先了解:

1、鎖定讀取和一致性非鎖定讀取

鎖定讀取:在一個交易中,主動給讀加鎖,如SELECT ... LOCK IN SHARE MODE 和SELECT ... FOR UPDATE。分別加上了行共享鎖和行排他鎖。鎖的分類可見我以前的分析文章:你應該了解的MySQL鎖定分類)。

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html

一致性非鎖定讀取:InnoDB使用MVCC向事務的查詢提供某個時間點的資料庫快照。查詢會看到在該時間點之前提交的事務所所做的更改,而不會看到稍後或未提交的事務所所做的更改(本事務除外)。也就是說在開始了事務之後,事務看到的資料就都是事務開啟那一刻的資料了,其他事務的後續修改不會在本次事務中可見。

Consistent read是InnoDB在RC和RR隔離層級處理SELECT語句的預設模式。一致性非鎖定讀取不會對其存取的表設定任何鎖,因此,在對錶執行一致性非鎖定讀取的同時,其它事務可以同時並發的讀取或修改它們。

https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

2、目前讀取和快照讀取

#目前讀取

讀取的是最新版本,像UPDATE、DELETE、INSERT、SELECT ...  LOCK IN SHARE MODE、SELECT ... FOR UPDATE這些動作都是一種目前讀,為什麼叫目前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他並發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

快照讀

讀取的是快照版本,也就是歷史版本,像不加鎖的SELECT操作就是快照讀,即不加鎖的非阻塞讀取;快照讀取的前提是隔離級別不是未提交讀取和序列化讀取級別,因為未提交讀取總是讀取最新的資料行,而不是符合當前事務版本的資料行,而序列化讀則會對錶加鎖

3、隱式鎖定和明確鎖定

隱含鎖定

InnoDB在交易執行過程中,使用兩階段鎖定協定(不主動進行顯示鎖定的情況):

  • 隨時都可以執行鎖定,InnoDB會根據隔離等級在需要的時候自動加鎖;
  • 鎖定只有在執行commit或rollback的時候才會釋放,並且所有的鎖都在同一時刻被釋放。

明確鎖定

  • InnoDB也支援透過特定的語句進行顯示鎖定(儲存引擎層)

    select ... lock in share mode //共享锁
    select ... for update //排他锁
    登入後複製
  • MySQL Server層的顯示鎖定:

    lock table
    unlock table
    登入後複製

了解完上面的概念後,我們來看下InnoDB的事務具體是怎麼實現的(下面的讀都指的是非主動加鎖的select)

                    則所』
交易對目前被讀取的資料不加鎖; 交易在更新某資料的瞬間(就是發生更新的瞬間),必須先加
行級共享鎖定
,直到交易結束才釋放。
交易對目前已讀取的資料加上,一旦讀完該行,立即釋放該行級共享鎖定;事務在更新某資料的瞬間(就是發生更新的瞬間),必須先對其加
行級排他鎖定
,直到事務結束才釋放。
交易在讀取某資料的瞬間(就是開始讀取的瞬間),必須先將其加上,直到交易結束才釋放;交易在更新某資料的瞬間(就是發生更新的瞬間),必須先將其加
行級排他鎖定
,直到事務結束才釋放。
交易在讀取資料時,必須先將其加上 ,直到交易結束才釋放;交易在更新資料時,必須先對其加
表格級排他鎖定
,直到交易結束才釋放。
事务隔离级别 实现方式
未提交读(RU)事务对当前被读取的数据不加锁,都是当前读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。
提交读(RC) 事务对当前被读取的数据不加锁,且是快照读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。
可重复读(RR)事务对当前被读取的数据不加锁,且是快照读

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。

通过间隙锁,在这个级别MySQL就解决了幻读的问题

通过快照,在这个级别MySQL就解决了不可重复读的问题
序列化读(S) 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读

事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。

可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读问题,大大提升了数据库的并发能力。

一些常见误区

幻读到底包不包括了delete的情况?

不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是RR级别,当然MySQL使用MVCC在RC级别就解决了这个问题)

幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是S级别,当然MySQL使用间隙锁在RR级别就解决了这个问题)

网上很多文章提到幻读和提交读的时候,有的说幻读包括了delete的情况,有的说delete应该属于提交读的问题,那到底真相如何呢?我们实际来看下MySQL的官方文档(如下)

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html

可以看到,幻读针对的是结果集前后发生变化,所以看起来delete的情况应该归为幻读,但是我们实际分析下上面列出的标准SQL在RR级别的实现原理就知道,标准SQL的RR级别是会对查到的数据行加行共享锁,所以这时候其他事务想删除这些数据行其实是做不到的,所以在RR下,不会出现因delete而出现幻读现象,也就是幻读不包含delete的情况。

MVCC能解决了幻读问题?

网上很多文章会说MVCC或者MVCC+间隙锁解决了幻读问题,实际上MVCC并不能解决幻读问题。如以下的例子:

begin;

#假设users表为空,下面查出来的数据为空

select * from users; #没有加锁

#此时另一个事务提交了,且插入了一条id=1的数据

select * from users; #读快照,查出来的数据为空

update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照

select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照

commit;
登入後複製

可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。如下:

begin;

#假设users表为空,下面查出来的数据为空

select * from users lock in share mode; #加上共享锁

#此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待

select * from users; #读快照,查出来的数据为空

update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新

select * from users; #读快照,查出来的数据为空

commit;

#事务B提交成功并插入数据
登入後複製

注意,RR级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的

原文地址:https://segmentfault.com/a/1190000025156465

作者: X先生

【相关推荐:mysql视频教程

以上是淺析MySQL中的事務隔離級別,聊聊其實現原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:segmentfault.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!