面試官:換人,他怎麼連MVCC和事務隔離級別的關係都不知道?

點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。

本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

前言

數據庫存在幾種事務隔離級別我想不用我說,大家也都知道的吧?

什麼?還不知道?還不知道就自己去補課,我默認大家都知道了。算了我是暖男,在貼一下給大家看看,下次可別忘了哈。

有四種:

  • 讀未提交(READ UNCOMMITTED):一個事務還沒提交時,它做的變更就能被別的事務看到。
  • 讀提交(READ COMMITTED):一個事務提交之後,它做的變更纔會被其他事務看到。
  • 可重複讀(REPEATABLE READ):一個事務執行過程中看到的數據,總是跟這個事務在啓動時看到的數據是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
  • 串行化(SERIALIZABLE):對於同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”,當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。

隔離級別解決了哪些問題大家也應該都是知道的分別有:

  • 髒讀(dirty read):如果一個事務讀到了另一個未提交事務修改過的數據。

  • 不可重複讀(non-repeatable read):如果一個事務只能讀到另一個已經提交的事務修改過的數據,並且其他事務每對該數據進行一次修改並提交後,該事務都能查詢得到最新值。

  • 幻讀(phantom read):如果一個事務先根據某些條件查詢出一些記錄,之後另一個事務又向表中插入了符合這些條件的記錄,原先的事務再次按照該條件查詢時,能把另一個事務插入的記錄也讀出來。

如何設置事務的隔離級別?

我們可以通過下邊的語句修改事務的隔離級別:

SET [GLOBAL|SESSIONTRANSACTION ISOLATION LEVEL level;//等級就是上面的幾種

怎麼啓動的?

啓動事務一般就是:

  1. 顯式啓動事務語句, begin 或 start transaction,配套的提交語句是commit,回滾語句是rollback。
  2. set autocommit=0,這個命令會將這個線程的自動提交關掉,意味着如果你只執行一個select語句,這個事務就啓動了,而且並不會自動提交。這個事務持續存在直到你主動執行commit 或 rollback 語句,或者斷開連接。

我在書中看到都是不要建議大家使用自動的,我看了一下我們的場景,倒是很少主動啓動事務的,因爲我們單個庫的場景不多,更多都是分佈式事物。

但是長事務是大家需要注意的,因爲一旦set autocommit=0自動開啓事務,所有的查詢也都會在事務裏面了,有慢SQL那數據庫也容易被拖垮的。

我最近就遇到了這樣的問題,數據庫經常接到報警,其中就有長事務導致的問題。

視圖

首先得說一下,我後面所有的知識都是基於InnoDB的,因爲MyISAM不支持事務。

視圖,這是事務隔離實現的根本,數據庫裏面會創建一個視圖,訪問的時候以視圖的邏輯結果爲準。

在MySQL裏,有兩個“視圖”的概念:

  • 一個是view,它是一個用查詢語句定義的虛擬表,在調用的時候執行查詢語句並生成結果。創建視圖的語法是create view … ,而它的查詢方法與表一樣。
  • 另一個是InnoDB在實現MVCC時用到的一致性讀視圖,即consistent read view,用於支持RC(Read Committed,讀提交)和RR(Repeatable Read,可重複讀)隔離級別的實現。

可重複讀隔離級別下,這個視圖是在事務啓動時創建的,整個事務存在期間都用這個視圖。

在正式介紹之前,我還需要介紹一下版本鏈

版本連

可重複讀隔離級別下,事務在啓動的時候就“拍了個快照”,注意,這個快照是基於整庫的。

你肯定會說,這怎麼可能?如果一個庫有100G,那麼我啓動一個事務,MySQL就要拷貝100G的數據出來,這個過程得多慢啊,這誰頂得住啊?可是你回頭一想,平時的事務執行起來不是很快麼?

實際上,數據庫並不需要拷貝出這100G的數據,那快照怎麼實現的?

InnoDB裏面每個事務有一個唯一的事務ID,叫作transaction id,它是在事務開始的時候向InnoDB的事務系統申請的,是按申請順序嚴格遞增的。

每行數據也都是有多個版本的,每次事務更新數據的時候,都會生成一個新的數據版本,並且把transaction id賦值給這個數據版本的事務ID,記爲row trx_id。同時,舊的數據版本要保留,並且在新的數據版本中,能夠有信息可以直接拿到它。

也就是說,數據表中的一行記錄,其實可能有多個版本(row),每個版本有自己的row trx_id

這是一個隱藏列,還有另外一個roll_pointer:每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日誌中,然後這個隱藏列就相當於一個指針,可以通過它來找到該記錄修改前的信息

兩者都在InnoDB的聚簇索引中,大概就長這樣:

undo log的回滾機制也是依靠這個版本鏈,每次對記錄進行改動,都會記錄一條undo日誌,每條undo日誌也都有一個roll_pointer屬性(INSERT操作對應的undo日誌沒有該屬性,因爲該記錄並沒有更早的版本),可以將這些undo日誌都連起來,串成一個鏈表,所以現在的情況就像下圖一樣:

接下來可以說一下事務隔離級別和MVCC的關係了,下面的例子是一個版本鏈,事務id大家可以看出,三個事務分別作了不同的事情。

讀提交隔離級別下,這個視圖是在每個SQL語句開始執行的時候創建的,在這個隔離級別下,事務在每次查詢開始時都會生成一個獨立的ReadView。

可重複讀,在第一次讀取數據時生成一個ReadView,對於使用REPEATABLE READ隔離級別的事務來說,只會在第一次執行查詢語句時生成一個ReadView,之後的查詢就不會重複生成了,所以一個事務的查詢結果每次都是一樣的。

這裏需要注意的是,讀未提交隔離級別下直接返回記錄上的最新值,沒有視圖概念,也就是圖中丙丙那一欄,髒讀,幻讀,不可重複讀都有可能發生。

串行化隔離級別下直接用加鎖的方式來避免並行訪問。

所以之前有小夥伴我問的時候經常說錯,mvcc裏面跟事務隔離級別相關的,只有可重複讀和讀已提交這兩種。

我工作以來,我所有接觸的公司的數據庫隔離級別默認都是讀已提交,不過我們會在一些場景開啓可重複讀,序列化很少見。

可重複讀我們之前都是在跟訂單金額相關的場景去開啓的,還有很多數據修改過程也會用可重複度,因爲很多值是需要查詢出來,依據那個值做別的操作的,如果多次查詢的結果值不一樣,那後者也會受到影響。

序列化被稱爲數據庫隔離級別的黃金標準,它是絕大多數商業數據庫系統中提供的最高隔離級別,一些高度廣泛部署的系統甚至無法提供隔離級別與可序列化一樣高,金融的場景居多,性能也是最差的,但是銀行取錢你會在乎那幾秒麼?

有沒有發現銀行的ATM響應速度特別慢,他們的場景都是很嚴密的,各種事務,鎖,都是結合的,就是爲了保證結果的準確性。

大家可以用這個命令去看看自己公司或者自己現在使用的數據庫的隔離級別:

show variables 

資料參考:《MySQL 是怎樣運行的:從根兒上理解 MySQL》、《高性能MySQL》、《 MySQL 實戰 45 講》

相關資料

準備了很多學習資料給大家https://pan.baidu.com/s/1gM4Ea11ygHuMomT2VQ2aNQ

總結

從上邊的描述中我們可以看出來,所謂的MVCC(Multi-Version Concurrency Control ,多版本併發控制)指的就是在使用讀已提交(READ COMMITTD)、可重複讀(REPEATABLE READ)這兩種隔離級別的事務在執行普通的SELECT操作時訪問記錄的版本鏈的過程,這樣子可以使不同事務的讀-寫、寫-讀操作併發執行,從而提升系統性能。

這兩個隔離級別的一個很大不同就是:生成ReadView的時機不同,READ COMMITTD在每一次進行普通SELECT操作前都會生成一個ReadView,而REPEATABLE READ只在第一次進行普通SELECT操作前生成一個ReadView,數據的可重複讀其實就是ReadView的重複使用。

這樣去解釋這些技術,主要是希望大家對現象背後的本質多點思考,不然你去背出這幾種隔離級別,以及各種數據現象是沒有任何意義的,實際開發過程中真的出現了問題,你不懂本質以及過程,你去排查也會很難受的,到頭來還是要看書,看資料。

我是敖丙,一個在互聯網苟且偷生的程序員。

你知道的越多,你不知道的越多人才們的 【三連】 就是丙丙創作的最大動力,我們下期見!

注:如果本篇博客有任何錯誤和建議,歡迎人才們留言!


文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回覆【資料】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章