大家都知道事務的ACID四大特性剃根,其中隔離性代表事務的修改結果在什么時候能被其他事務看到皆的。這篇文章來介紹下數(shù)據(jù)庫中是如何實現(xiàn)事務隔離的找田。
隔離級別介紹
當數(shù)據(jù)庫上有多個事務同時執(zhí)行的時候控轿,就可能出現(xiàn)臟讀(dirty read)、不可重復讀(non reapeatable read)溶浴、幻讀(phantom read)的問題,為了解決這些問題管引,就有了“隔離級別”的概念士败。標準的隔離級別有:讀未提交(read uncommitted)、讀已提交(read commited)褥伴、可重復讀(repeatable read)串行化谅将。其中隔離級別越嚴格,安全性越高重慢,但數(shù)據(jù)庫的并發(fā)性能也就越低饥臂,往往需要在兩者之間找一個平衡點。
隔離的實現(xiàn)
隔離的實現(xiàn)主要有讀寫鎖和MVCC(Multi-Version Concurrency Control)多版本并發(fā)處理方式似踱。
1.讀寫鎖
最簡單直接的的事務隔離實現(xiàn)方式隅熙,每次讀操作需要獲取一個共享鎖,每次寫操作需要獲取一個寫鎖核芽。共享鎖之間不會產(chǎn)生互斥囚戚,共享鎖和寫鎖之間、以及寫鎖與寫鎖之間會產(chǎn)生互斥轧简。當產(chǎn)生鎖競爭時驰坊,需要等待其中一個操作釋放鎖后,另一個操作才能獲取到鎖哮独。
2. MVCC
在讀寫鎖中拳芙,讀和寫的排斥作用大大降低了事務的并發(fā)效率,于是人們又提出了能不能讓讀寫之間也不沖突的方法皮璧,就是讀取數(shù)據(jù)時通過一種類似快照的方式將數(shù)據(jù)保存下來态鳖,這樣讀鎖就和寫鎖不沖突了。不同的事務session會看到自己特定版本的數(shù)據(jù)恶导,即使其他的事務更新了數(shù)據(jù)勉失,但是對本事務仍然不可見艾栋,本事務看到的數(shù)據(jù)始終是第一次查詢到的數(shù)據(jù)。在數(shù)據(jù)庫中,這個快照的處理方式叫多版本并發(fā)控制(Multi-Version Concurrency Control)串远。這種方式真正實現(xiàn)了非阻塞讀螟蝙,只有在寫操作時才需要加行級鎖葡秒,因此并發(fā)效率更高斯入。
在各個數(shù)據(jù)庫系統(tǒng)的,MVCC的實現(xiàn)機制不盡相同蕉拢,下面來詳細介紹一下InnoDB是如何實現(xiàn)MVCC的特碳,主要討論可重復讀級別的實現(xiàn)诚亚。
首先,需要了解兩個概念:ReadView午乓、undo log站宗、可見性判斷算法
ReadView
ReadView其實就是上面提到的快照,每個事務在啟動后第一次執(zhí)行查詢時會創(chuàng)建一份快照益愈,一個事務快照的創(chuàng)建過程可以概括為:
- 查看當前所有的未提交并活躍的事務梢灭,存儲在數(shù)組中
- 選取未提交并活躍的事務中最小的XID,記錄在快照的xmin中
- 選取未提交事務中最大的XID蒸其,記錄快照在xmax中
ReadView主要是用來做可見性判斷的敏释,即通過ReadView可以知道:哪些事務的提交結果對當前事務可見,哪些事務的提交結果對當前事務不可見摸袁。關于如何判斷可見性钥顽,后面部分會對可見性判斷算法做出介紹。不過靠汁,我們可以先思考一個問題蜂大,在可重復讀隔離級別中,哪些事務的提交結果對當前事務可見呢膀曾?
undo log
剛才介紹了ReadView的基礎概念,提到了ReadView是事務的快照阳啥,但是通過ReadView僅僅能知道哪些事務的提交結果對當前事務可見添谊,可是還是不知道當前事務的數(shù)據(jù)快照在哪啊。undo log就是來解決這個問題的察迟。
undo log就是我們通常說的回滾日志斩狱,undo log存放的是數(shù)據(jù)的歷史記錄,也可以叫數(shù)據(jù)的快照扎瓶。
當一個事務要提交修改時:
1.會用排他鎖鎖定該行
2.將該行修改前的值Copy到undo log segment(回滾段)所踊。
3.修改當前行的值,將該行的回滾指針指向undo log中修改前的行概荷。
下圖描述了數(shù)據(jù)行和回滾段的數(shù)據(jù)關系秕岛。
如上圖,回滾日志使用鏈表組織起來的误证,鏈表的每個節(jié)點都是一個數(shù)據(jù)的版本继薛。InnoDB在每行記錄后面添加了三個字段:
DB_ROW_ID: 包含一個隨著新行插入而單調(diào)遞增的行ID, 當由innodb自動產(chǎn)生聚集索引時,聚集索引會包括這個行ID的值愈捅,否則這個行ID不會出現(xiàn)在任何索引中遏考。
DB_TRX_ID: 最后一次對本行提交修改的事務ID。同時蓝谨,在回滾段中的每條記錄灌具,也包含著該條日志對應的事務ID青团。
DB_ROLL_PTR:指向?qū)懭牖貪L段(rollback segment)的 undo log record (撤銷日志記錄記錄)】ч梗回滾段的數(shù)據(jù)結構是鏈表督笆,如果需要找到指定版本的數(shù)據(jù),需要通過DB_ROLL_PTR指針沿著鏈表遍歷回滾段截歉。
可見性判斷算法
在介紹ReadView的時候胖腾,我們提出了可重復讀隔離級別中事務的可見性問題。這個問題答案很簡單瘪松,在可重復讀隔離級別中咸作,對于當前事務tx_cur來說,tx_cur開始查詢之前的已提交事務都對tx_cur都可見宵睦,在tx_cur開始查詢之前的未提交事務和tx_cur開始查詢之后的所有事務對tx_cur均不可見记罚。下面用一張草圖解釋一下。
如圖所示壳嚎,tx1-tx6分別是按時間順序的6個數(shù)據(jù)庫事務桐智,假設當前啟動的事務是tx_cur,其中tx1-tx2是tx_cur開始查詢之前的已提交事務烟馅,tx3-tx5是tx_cur開始查詢時正在進行的活躍事務说庭,tx6是開始查詢之后的提交的事務。
在tx_cur啟動事務并開始第一次查詢時郑趁,會創(chuàng)建一個ReadView刊驴,ReadView中存儲的是當前正在活躍的所有未提交事務id。在ReadView之前的已提交事務對tx_cur可見寡润,在ReadView中以及ReadView之后的事務對tx_cur均不可見捆憎。
上面是一個簡單的事務可見性判斷過程,那么當前事務該如何找到正確版本的數(shù)據(jù)呢梭纹?這個需要結合undo log一起來說躲惰。
我們可以結合undo log那節(jié)的示意圖來看。
1.首先查詢行的DB_TRX_ID字段变抽,該字段記錄的是當前行最后提交的事務ID础拨,簡稱為tx_id。
2.通過ReadView判斷tx_id是否對tx_cur可見绍载。若可見太伊,即找到了正確版本的數(shù)據(jù);若不可見逛钻,則通過DB_ROLL_PTR指針找到undo log的上一個版本記錄僚焦,重復過程1。
總結
- 隔離的實現(xiàn)主要有讀寫鎖和MVCC(Multi-Version Concurrency Control)多版本并發(fā)處理方式曙痘。MVCC方式由于其讀寫不沖突的方式芳悲,相當于讀寫鎖效率更高立肘。
- undo log和ReadView通過可見性判斷算法實現(xiàn)了基本的MVCC,從而實現(xiàn)了事務的隔離名扛。