跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

提到MySQL的事務,我相信對MySQL有了解的同窗都能聊上幾句,不管是面試求職,仍是平常開發,MySQL的事務都跟咱們息息相關。html

而事務的ACID(即原子性Atomicity、一致性Consistency、隔離性Isolation、持久性Durability)能夠說涵蓋了事務的所有知識點,因此,咱們不只要知道ACID是什麼,還要了解ACID背後的實現,只有這樣,不管在平常開發仍是面試求職,都能無往而不利。mysql

爲了你們更好的閱讀體驗,對ACID的深刻分析將分爲上下兩篇。git

本篇爲上篇,主要圍繞ACID中的I,也就是「隔離性」展開,從基本概念,到隔離性的實現,最後以一個實戰案例進行融會貫通。github

嗯,看完你都能理解,那跟面試官侃半小時隔離性就沒問題了。面試

1.事務隔離性的基本概念

1.1 什麼是ACID中的Isolation,隔離性

Isolation,隔離性,也有人稱之爲併發控制(concurrency control)。事務的隔離性要求每一個事務讀寫的對象對其餘事務都是相互隔離的,也就是這個事務提交前,這個事務的修改內容對其餘事務都是不可見的。事務的隔離性,主要是解決不一樣事物之間的相互讀寫影響。算法

所謂的讀寫影響注意分爲三種:sql

  • 髒讀:讀到了別的事務還沒有提交(commit)的變動,別人沒提交,我讀到了。
  • 不可重複讀:別的事務提交了變動,被當前事務讀到了。而後致使本事務屢次select的結果不同,讀到了別的事務提交的內容。
  • 幻讀:也是讀到了別的事務提交的內容,可是跟上面的不一樣之處在於,讀到了本來不存在的記錄。

注意,不可重複讀,主要是讀到了別的事務update的內容。而幻讀,是讀到了別的事務insert的內容。數據庫

1.2 隔離性的隔離級別

爲了解決事務隔離性的問題,數據庫通常會有不一樣的隔離級別來解決相應的讀寫影響。數組

  • 讀未提交:一個事務B還沒提交,它的修改就被別的事務A讀到了。
  • 讀已提交:一個事務B提交後,它的修改被其餘事務A看到了。
  • 可重複讀:一個事物B提交前和提交後,事務A都沒法讀到事務B的變動。
  • 串行化:對同一行記錄,當出現不一樣事物的讀寫衝突時,是經過串行化的方式解決的,後一個事務必須等前一個事務完成才能執行。

不一樣隔離級別可以解決不一樣的隔離性問題。併發

跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

 

須要注意的是,這是標準事務隔離級別的定義。在MySQL的innodb引擎中,在可重複讀級別下,經過mvcc解決了幻讀的問題,具體實現咱們後面再講。

同時,須要注意的是,到目前爲止,咱們說的讀,都是」快照讀」,普通的select。後面咱們還會提到「當前讀」,是不同的哦。

2.事務隔離性的實現

要實現事務的隔離性,須要瞭解兩個方面的內容,一個是鎖,一個是多版本併發控制(MVCC)。

2.1 事務的行鎖

InnoDB中,實現了兩種標準的行級鎖:

  • 共享鎖(S Lock),也叫讀鎖,容許事務讀取一行數據。
  • 排它鎖(X Lock),也叫寫鎖,容許事務刪除或者更新一行數據(注意,這裏沒有提到插入哦,插入涉及到幻讀,能夠看文章最後的說明)

普通select語句不會有任何鎖,那麼如何得到共享鎖和排它鎖呢?

  • Select … lock in share mode語句可以得到共享鎖
  • Select … for update(特殊的select,用mysql簡單實現分佈式鎖常常用它)、Update、delete語句可以得到排它鎖

當一個事務A已經得到了行r的共享鎖,那麼另外一個事務B能夠馬上得到行r的共享鎖,由於不會改變r的數值,這種叫作鎖兼容。

若是這時候有事務C但願得到行r的排它鎖,那麼就必須等待事務A和事務B釋放行r的共享鎖以後,才能得到排它鎖,這種叫作鎖不兼容。

跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

 

普通的select不會對行上鎖,而select…lock in share mode會上共享鎖,select…for update會上排它鎖。

  • 對於普通的select的讀取方式,稱爲」快照讀「,也叫」一致性非鎖定讀「。
  • 對於帶鎖的select讀取,或者update tb set a = a+1(讀取a的當前值),稱爲「當前讀」,也叫「一致性鎖定讀」。

若是在update、insert的時候,不能進行select,那麼服務的併發訪問性能就太差了。所以,咱們平常的查詢,都是「快照讀」,不會上鎖,只有在update\insert\「當前讀」的時候,纔會上鎖。而爲了解決「快照讀」的併發訪問問題,就引入了MVCC。

2.2 多版本併發控制MVCC

若是說上面的行鎖是一種悲觀鎖,那麼MVCC就是一種樂觀鎖的實現方式,並且是一種很經常使用的樂觀鎖實現方式。

所謂多版本,就是一行記錄在數據庫中存儲了多個版本,每一個版本以事務ID做爲版本號。InnoDB 裏面每一個事務有一個惟一的事務 ID,是在事務開始的時候向InnoDB的事務系統申請的,而且按照申請順序嚴格遞增的。假如一行記錄被多個事務更新,那麼,就會產生多個版本的記錄。

以某一行數據做爲例子:

跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

 

通過兩次事務的操做,value從22變成了19,同時,保留了三個事務id,1五、2五、30。

在每一個記錄多版本的基礎上,須要利用「一致性視圖」,來作版本的可見性判斷。

這裏,咱們要區分MySQL裏面的兩個」視圖」概念:

  • 一個是view,經過語法create view … 實現,主要建立一個虛擬表,用來執行查詢語句。
  • 一個是InnoDB用來實現mvcc的一致性視圖(consistent read view),純邏輯概念,沒有物理結構,定義了在事務期間,你能看到哪些版本的數據。

咱們全文提到的「視圖」都是第二種,主要是支持InnoDB在「讀已提交」和「可重複讀」級別的併發訪問問題。

  • 「讀未說起」級別下,沒有一致性視圖
  • 「讀已提交」級別下,會在 每一個SQL開始執行的時候 建立一致性視圖
  • 「可重複讀」級別下,會在 每一個事務開始的時候 建立一致性視圖
  • 「串行化」級別下,直接經過加鎖避免併發問題

下面,咱們簡單介紹一下建立一致性視圖的邏輯。

以「可重複讀」級別爲例。

  • 當一個事務開啓的時候,會向系統申請一個新事務id
  • 此時,可能還有多個正在進行的其餘事務沒有提交,所以在瞬時時刻,是有多個活躍的未提交事務id
  • 將這些未提交的事務id組成一個數組,數組裏面最小的事務id記錄爲低水位,當前系統建立過的事務id的最大值+1記錄爲高水位
  • 這個數組array 和 高水位,就組成了「一致性視圖」。

有了一致性視圖後,咱們就能夠判斷一行數據的多版本可見性了,不管是「讀已提交」仍是「可重複讀」級別,可見性判斷規則是同樣的,區別在於建立快照(一致性視圖)的時間。

在當前事務中,讀取其餘某一行的記錄,對其中的版本號的可見性判斷有五種狀況(建議本身跟着捋一捋,挺重要的):

  • 若是版本號小於「低水位」,說明事務已經提交,那確定 可見;
  • 若是版本號大於「高水位」,說明這行數據的這個事務id版本是在快照後產生的,那確定 不可見;
  • 若是版本號在事務數組array中,說明這個事務還沒提交,因此 不可見;
  • 若是版本號不在事務數組array中,且低於高水位,說明這個事務已經提交,因此 可見;
  • 固然,不管何時,本身的事務id中的任何變化,都是可見的

能夠看看下面這個例子,更容易理解。

系統建立過的事務id:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

事務A啓動,拍個快照

此時未提交的事務id有:7,8,9

一致性視圖:數組array[7,8,9] + 高水位16(15+1)

對於任意一行數據的可見性判斷以下:

1)小於7的,可見

2)大於16的,說明是快照後產生的,不可見

3)10-15,不在數組array中,說明已經提交了,可見

4)7,8,9在array中,說明未提交,不可見

兩個重要結論:

  • InnoDB 利用了「全部數據都有多個版本」的這個特性,實現了「秒級建立快照」的能力。
  • MVCC的實現,就是根據當前事務的事務id爲依據建立「一致性視圖」,利用一致性視圖來判斷數據版本的可見性。

3.隔離性實戰

下面,咱們來兩個實戰案例,將上面的基礎概念與實現融會貫通吧。

1)併發select&update 案例

id=1 的value初始爲1。

跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

 

咱們看下,在不一樣隔離級別,Time五、Time七、Time9事務A查詢到的value 分佈爲多少。

  • 「讀未提交」:2,2,2
  • 「讀以提交」:1,2,2
  • 「可重複讀」:1,1,2
  • 串行化:1,1,2(注意,這裏在事務A提交前,事務B都會阻塞,直到事務A提交後才能執行)

2)併發update案例

id=1 的value初始爲1,在可重複讀級別:

跟面試官侃半小時MySQL事務隔離性,從基本概念深刻到實現

 

咱們看一下,你猜猜事務A和事務B讀取的value是多少?

答案是:1 和 3

可能會產生困惑,事務A在啓動後快照,因此讀到了1是正常的,可是事務2在啓動的時候快照了,而後在本身的事務中+1,怎麼會讀到3而不是2呢?

緣由很簡單,即便是在可重複讀的級別,事務 更新數據 的時候,只能用當前讀(想一想也能理解,否則update就出現數據不一致了)。

若是當前的記錄的行鎖被其餘事務佔用的話,就須要進入鎖等待。而讀提交的邏輯和可重複讀的邏輯相似,它們最主要的區別是:在可重複讀隔離級別下,只須要在事務開始的時候建立一致性視圖,以後事務裏的其餘查詢都共用這個一致性視圖;在讀提交隔離級別下,每個語句執行前都會從新算出一個新的視圖。

這裏,咱們須要注意的是事務的啓動時機。

  • begin/start transaction 命令並非一個事務的起點,在執行到它們以後的第一個操做 InnoDB 表的語句,事務才真正啓動,一致性視圖是在執行第一個快照讀語句時建立的。
  • 若是你想要立刻啓動一個事務,可使用 start transaction with consistent snapshot 這個命令,一致性視圖是在執行 start transaction with consistent snapshot 時建立的。

4.關於幻讀

首先明確一下,什麼是幻讀?開篇介紹了什麼是幻讀,這裏再申明一下幻讀出現的場景

  • 第一:事務的隔離級別爲可重複讀,且是當前讀
  • 第二:幻讀僅專指新插入的行,在範圍查詢中,後一次查詢出現了新的數據行。

前文已經提到了,對於普通數據庫,須要到可串行化的隔離級別才能解決幻讀問題。

而對於InnoDB存儲引擎來講,在可重複讀級別下就能解決幻讀問題。

InnoDB存儲引擎有三種行鎖算法:

  • 行鎖:當個行記錄上的鎖
  • 間隙鎖:Gap Lock,鎖定一個範圍,但不包含記錄自己
  • Next-Key Lock:就是行鎖+間隙鎖,同時鎖上一個範圍,而且鎖定記錄自己

InnoDB就是經過Next-Key Lock解決了幻讀的問題,具體內容能夠看我以前的文章: 前阿里數據庫專家總結的MySQL裏的各類鎖(下篇)

 

看到這裏了,原創不易,點個關注、點個贊吧,你最好看了~

知識碎片從新梳理,構建Java知識圖譜:https://github.com/saigu/JavaKnowledgeGraph(歷史文章查閱很是方便)

掃碼關注個人公衆號「阿丸筆記」,第一時間獲取最新更新。同時能夠免費獲取海量Java技術棧電子書、各個大廠面試題。

阿丸筆記

相關文章
相關標籤/搜索