深入理解mysql的事務隔離級別和底層實現原理

2021-09-26 03:32:42 字數 4121 閱讀 3224

永續性,我們就不講了,易懂。

在同乙個事務內部的一組操作必須全部執行成功(或者全部失敗)。

為了保證事務操作的原子性,必須實現基於日誌的redo/undo機制:將所有對資料的更新操作都寫入日誌,如果乙個事務中的一部分操作已經成功,但以後的操作,由於斷電/系統崩潰/其它的軟硬體錯誤而無法繼續,則通過回溯日誌,將已經執行成功的操作撤銷,從而達到「全部操作失敗」的目的。 最常見的場景是,資料庫系統崩潰後重啟,此時資料庫處於不一致的狀態,必須先執行乙個crash recovery的過程:讀取日誌進行redo(重演將所有已經執行成功但尚未寫入到磁碟的操作,保證永續性),再對所有到崩潰時尚未成功提交的事務進行undo(撤銷所有執行了一部分但尚未提交的操作,保證原子性)。crash recovery結束後,資料庫恢復到一致性狀態,可以繼續被使用。

某個應用在執行轉帳的資料庫操作時,必須在同乙個事務內部呼叫對帳戶a和帳戶b的操作,才能保證資料的一致性。

但是,原子性並不能完全保證一致性。在多個事務並行進行的情況下,即使保證了每乙個事務的原子性,仍然可能導致資料不一致的結果。例如,事務1需要將100元轉入帳號a:先讀取帳號a的值,然後在這個值上加上100。但是,在這兩個操作之間,另乙個事務2修改了帳號a的值,為它增加了100元。那麼最後的結果應該是a增加了200元。但事實上,事務1最終完成後,帳號a只增加了100元,因為事務2的修改結果被事務1覆蓋掉了。

簡而言之,就是:原子性僅能夠保證單個事務的一致性。就像redis一樣,也只能保證單操作的執行緒安全,並不能保證多操作下的執行緒安全。

按照我個人的理解,在事務處理的acid屬性中,一致性是最基本的屬性,其它的三個屬性都為了保證一致性而存在的。

為了保證併發情況下的一致性,引入了隔離性,即保證每乙個事務能夠看到的資料總是一致的,就好象其它併發事務並不存在一樣。

資料庫四種隔離級別,以及常見的幾種讀異常,大家應該都是耳熟能詳的,但資料庫底層是怎麼實現隔離性的呢?都採用了哪些技術呢? 主要有兩個技術:mvcc(多版本併發控制)和鎖。

(1)mvcc(多版本併發控制)

多版本併發控制,顧名思義,在併發訪問的時候,資料存在版本的概念,可以有效地提公升資料庫併發能力,常見的資料庫如mysql、ms sql server、ibm db2、hbase、mongodb等等都在使用。

簡單講,如果沒有mvcc,當想要讀取的資料被其他事務用排它鎖鎖住時,只能互斥等待;而這時mvcc可以通過提供歷史版本從而實現讀取被鎖的資料的歷史版本,從而避免了互斥等待。

innodb採用的mvcc實現方式是:在需要時,通過undo日誌構造出歷史版本。

(2)鎖

1) 鎖的分類

若事務t對資料物件a加上s鎖,則事務t只能讀a;其他事務只能再對a加s鎖,而不能加x鎖,直到t釋放a上的s鎖。這就保證了其他事務可以讀a,但在t釋放a上的s鎖之前不能對a做任何修改。

若事務t對資料物件a加上x鎖,則只允許t讀取和修改a,其它任何事務都不能再對a加任何型別的鎖,直到t釋放a上的鎖。它防止任何其它事務獲取資源上的鎖,直到在事務的末尾將資源上的原始鎖釋放為止。在更新操作(insert、update 或 delete)過程中始終應用排它鎖。

注意:排他鎖會阻止其它事務再對其鎖定的資料加讀或寫的鎖,但是不加鎖的就沒辦法控制了。

行鎖,顧名思義,是加在索引行(對!是索引行!不是資料行!)上的鎖。比如select * from user where id=1 and id=10 for update,就會在id=1id=10的索引行上加record lock。

間隙鎖,它會鎖住兩個索引之間的區域。比如select * from user where id>1 and id<10 for update,就會在id為(1,10)的索引區間上加gap lock。

也叫間隙鎖,它是record lock + gap lock形成的乙個閉區間鎖。比如select * from user where id>=1 and id<=10 for update,就會在id為[1,10]的索引閉區間上加next-key lock。

這樣組合起來就有,行級共享鎖,表級共享鎖,行級排它鎖,表級排它鎖。

2) 什麼時候會加鎖?

在資料庫增刪改查四種操作中,insert、delete和update都是會加排它鎖(exclusive locks)的,而select只有顯式宣告才會加鎖:

3) 四種隔離級別

不同的隔離級別是在資料可靠性和併發性之間的均衡取捨,隔離級別越高,對應的併發效能越差,資料越安全可靠。

顧名思義,事務之間可以讀取彼此未提交的資料。機智如你會記得,在前文有說到所有寫操作都會加排它鎖,那還怎麼讀未提交呢?

機智如你,前面我們介紹排它鎖的時候,有這種說明: 排他鎖會阻止其它事務再對其鎖定的資料加讀或寫的鎖,但是對不加鎖的讀就不起作用了。

read uncommitted隔離級別下, 讀不會加任何鎖。而寫會加排他鎖,並到事務結束之後釋放。

例項1:

查-寫:查並沒有阻止寫,表明查肯定並沒有加鎖,要不寫肯定就阻塞了。寫很明顯,會加排它鎖的。

例項2: 寫-寫:阻塞,表明,寫會加排它鎖。

顧名思義,事務之間可以讀取彼此已提交的資料。

innodb在該隔離級別(read committed)寫資料時,使用排它鎖, 讀取資料不加鎖而是使用了mvcc機制。

因此,在讀已提交的級別下,都會通過mvcc獲取當前資料的最新快照,不加任何鎖,也無視任何鎖(因為歷史資料是構造出來的,身上不可能有鎖)。

但是,該級別下還是遺留了不可重複讀和幻讀問題:mvcc版本的生成時機: 是每次select時。這就意味著,如果我們在事務a中執行多次的select,在每次select之間有其他事務更新了我們讀取的資料並提交了,那就出現了不可重複讀,即:重複讀時,會出現資料不一致問題,後面我們會講解超支現象,就是這種引起的。

read committed級別不同的是mvcc版本的生成時機,即:一次事務中只在第一次select時生成版本,後續的查詢都是在這個版本上進行,從而實現了可重複讀

但是因為mvcc的快照只對讀操作有效,對寫操作無效,舉例說明會更清晰一點: 事務a依次執行如下3條sql,事務b在語句1和2之間,插入10條age=20的記錄,事務a就幻讀了。

1. select count(1) from user where age=20;

-- return 0: 當前沒有age=20的

2. update user set name=test where age=20;

-- affects 10 rows: 因為事務b剛寫入10條age=20的記錄,而寫操作是不受mvcc影響,能看到最新資料的,所以更新成功,而一旦操作成功,這些被操作的資料就會對當前事務可見

3. select count(1) from user where age=20;

-- return 10: 出現幻讀

repeatable read級別,可以防止大部分的幻讀,但像前邊舉例讀-寫-讀的情況,使用不加鎖的select依然會幻讀。

大殺器,該級別下,會自動將所有普通select轉化為select ... lock in share mode執行,即針對同一資料的所有讀寫都變成互斥的了,可靠性大大提高,併發性大大降低。

讀-寫,寫-寫均互斥。

4)總結:幾類讀異常

讀-寫-讀,引起的異常

檢視表的加鎖情況:select * from information_schema.innodb_locks;事務狀態select * from information_schema.innodb_trx;

深入理解oracle的事務隔離性

在oracle關聯式資料庫中,我們先來看下面這個問題 a事務 select from t where id 10 and id 10000 b事務 update t set id 45000 where id 4501 兩個事務按下面的順序執行 a事務 commit b事務 commit 也就是a事...

深入理解oracle的事務隔離性

在oracle關聯式資料庫中,我們先來看下面這個問題 a事務 select from t where id 10 and id 10000 b事務 update t set id 45000 where id 4501 兩個事務按下面的順序執行 a事務 commit b事務 commit 也就是a事...

二 深入理解MySql鎖與事務隔離級別

mvcc主要是為了提高高併發的讀寫效能,不用加鎖就能讓多個事務併發讀寫 對於事務id為12的操作,先查詢select from account 建立了查詢快照,記錄執行sql這一刻最大的已提交事務id 快照點已提交最大事務id 對於事務13的操作,先刪除id 1的記錄,然後更新id 2的記錄,再提交...