看了這篇文章,不再用擔憂面試官文MySQL事務了!!!

深刻淺出MySQL事務

事務是保證一組數據庫的操做,要麼所有成功,要麼所有失敗,這些操做必須保證是一體的,能夠理解爲事務是併發控制的一個基本單位,事務的的四大特性ACID是事務的基礎。在MySQL中,事務的支持是在引擎層出現的。在這篇文章中,咱們將會重點講解事務的四大特性ACID、多版本控制MVCC、當前讀和一致性讀。html

一、事務的四大特性ACID

1.1原子性

  • 概念

原子性是指一個事務是一個不可分割的單位,其中的操做要麼成功,要麼失敗,保證了這些操做是一體的,若是操做執行失敗,則已經執行的語句必須回滾退回事務以前的狀態。mysql

  • 實現原理

實現原子性的關鍵就是當發生錯誤時可以回滾,InnoDB實現回滾依賴於undo log(回滾日誌),當事務對數據庫進行修改時,InnoDB會生成對應的undo log日誌,若是在事務執行過程當中發生了錯誤,就須要調用rollback來實現事務的回滾,這就利用undo log中的信息將數據回滾到修改以前的狀態,修改就是作相反的工做,若是是insert,修改就是delete。sql

舉個例子,當事務執行update操做時,undo log中會包含被修改的主鍵、列、以及列在修改先後的信息,當回滾時,能夠利用undo log內的信息恢復到以前的狀態。數據庫

1.2隔離性

  • 概念

事務的隔離性指的是在併發環境中,併發的事務是相互隔離的,即一個事務不能被其它的事務所幹擾。不一樣的事務在併發操做相同的數據時,每一個事務都有各自完整的空間,即一個事務內部的操做及使用的數據對其它併發事務是隔離的,併發執行的各個事務之間不能互相干擾。編程

在學習事務的隔離性以前,咱們先學習下事務中併發會帶來什麼問題。數組

  • 事務的併發帶來的問題

髒讀:一個事務讀取了已被另外一個事務修改、但還沒有提交的數據,若是這個事務對數據屢次修改,並未提交,則會致使其它併發的事務讀取的數據不一致,這種狀況被稱爲髒讀。緩存

不可重複讀:事務A屢次讀取同一個數據,事務B在事務A屢次讀取的過程當中,對數據作了更新並提交,致使事務A屢次讀取同一數據時,結果一致。安全

幻讀:幻讀與不可重複讀相似。它發生在一個事務(T1)讀取了幾行數據,接着另外一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些本來不存在的記錄,就好像發生了幻覺同樣,因此稱爲幻讀。markdown

  • 隔離級別

在SQL規範中,定義了4個事務的隔離級別,分別爲讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重複讀(Repeatable Read)和串行化(Serializable),隔離級別越低,系統開銷就越低,可支持的併發就越高,可是隔離性也就越差。併發

①讀未提交(RU):一個事務能夠讀取到另一個事務未提交的數據,該隔離級別最低,容許髒讀、不可重複讀和幻讀。

②讀提交(RC):一個事務能夠讀取到另外一個事務已提交的數據,該隔離級別能夠防止髒讀,可是會出現不可重複讀和幻讀。

③可重複讀(RR):可重複讀是指在事務處理的過程當中,屢次讀取同一個數據時,其值和事務開始時刻是一致的,該隔離級別能夠防止髒讀、不可重複讀,可是會出現髒讀。

④串行化(Serializable):全部事物都被串行執行,即事務只能一個個的進行處理,不能併發執行,是事務隔離的最高級別,可是帶來的影響是併發效率的降低。

總結

隔離級別 髒讀 不可重複讀 幻讀
Read-Uncommitted YES YES YES
Read-Committed NO YES YES
Repeatable-Read NO NO YES
Serializable NO NO NO

NOTE:在InnoDB支持的事務中,其默認隔離級別是可重複讀,由於他在讀取的數據中加入了間隙鎖,因此這種隔離級別的效果同串行化效果相同,能夠防止幻讀。

1.3持久性

  • 概念

持久性指的是事務一旦提交,它對數據庫的改變就應該是永久性的,接下來的其它操做不會對其有任何的影響。

  • 實現原理

持久性依賴於redo log,爲了更流暢的講解如下的內容,咱們先了解下Buffer Pool,Buffer Pool是InnoDB提供的緩存,它包含了磁盤中部分數據頁的映射,當從數據庫讀取數據時,會首先從Buffer Pool中讀取,若是Buffer Pool中沒有,則從磁盤讀取後放入Buffer Pool。當向數據庫寫入數據時,會首先寫入Buffer Pool,Buffer Pool中修改的數據會按期刷新到磁盤中(這一過程稱爲刷髒)。它的做用就是爲了提升數據的讀寫效率。

咱們以數據修改的流程來說解怎麼實現持久性,當數據修改時,除了修改Buffer Pool()中的數據,還會在redo log中記錄此次操做,當事務提交時,會調用fsync接口對redo log進行刷盤。若是MySQL宕機,重啓時能夠讀取redo log中的數據,對數據庫進行恢復。redo log採用的是WAL(Write-ahead logging),全部修改先寫入日誌,再更新到Buffer Pool,保證了數據不會因MySQL宕機而丟失,從而知足了持久性要求。

1.4一致性

  • 概念

事務的一致性是指事務的執行不能破壞數據的完整性和一致性,事務的執行先後,數據庫必須保證處於一致性的狀態,即事務的執行結果必須使得數據庫從一個一致性狀態轉變成另外一個一致性狀態。所以當事務成功提交後,數據庫裏的數據處於一致性狀態,若是在事務執行過程當中發生了錯誤,這些未完成的事務已經修改了一部分數據到數據庫,這時數據庫裏的數據就是不一致的狀態。

  • 實現原理

事務的一致性是實現的最終目標,其實現的前提是保證事務中的原子性、隔離型和持久性。

二、MVCC的實現

2.1基本概念

MVCC全稱是Multi-Version Concurrency Control,即多版本併發控制,MVCC在MySQL InnoDB中的實現方式主要是爲了提升數據庫的併發性能,處理讀/寫操做的衝突。

在學習MVCC的原理以前,咱們先講解下什麼是快照讀。

2.2快照讀

快照讀是基於提升併發性能的考慮,快照讀的實現是基於多版本併發控制,咱們能夠能夠認爲MVCC是行鎖的一個變種,但它在不少狀況下,避免了加鎖操做,下降了開銷,既然是基於多版本,即快照讀可能讀到的並不必定是數據的最新版本,而有多是以前的歷史版本。準確的說,MVCC多版本併發控制的是維持一個數據的多個版本,使得讀寫操做沒有衝突。

2.3實現原理

InnoDB的默認隔離級別爲可重複讀,事務在啓動的時候就會拍快照,並且每一個事務都有惟一的一個事務ID,它是在事務開始時向InnoDB事務系統申請,申請順序是遞增的。每行數據也都是有多個版本的。每次事務更新數據的時候,都會生成一個新的數據版本,而且把 transaction id 賦值給這個數據版本的事務 ID,記爲 row trx_id。同時,舊的數據版本要保留,而且在新的數據版本中,可以有信息能夠直接拿到它。即數據表中的一行記錄就可能會有多個版本,每一個版本都有本身的row trx_id。

咱們以一段代碼爲例

建立表並插入數據

mysql> CREATE TABLE `t` (
 `id` int(11) NOT NULL,  `k` int(11) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB; insert into t(id, k) values(1,1),(2,2); 複製代碼

咱們對k依次進行更新

圖中的藍色矩形表示的是同一行數據的四個版本,即V1-->V2--->V3--->V4,它們分別被不一樣的事務所更新,在可重複讀的定義下,一個事務啓動的時候,可以看到在此以前提交的結果,可是在執行過程當中,對其它事務的更新是不可見的,那麼它是怎麼實現的呢?

事實上,InnoDB會爲每個事務構造一個數組,用於存放處於活動期間事務的ID,這裏的活動期間的事務能夠理解開啓可是還未提交的事務。數組裏的事務ID的最小值記爲低水位,在當前系統裏已經建立過的事務的最新值的ID+1構成高水位,這種視圖數組被稱爲一致性視圖

讀數據時,能夠根據row trx_id來判斷,根據事務的啓動瞬間分爲如下三種狀況

  • 啓動的事務ID在淺綠色部分,則表示這個版本是已提交的事務或者是當前事務本身生成,這個數據是可見的
  • 啓動的是ID在深綠色部分。則表示這個版本是未來的事務生成的,是不可見的
  • 啓動事務的ID落在黃色部分,則由如下兩種狀況

①若 row trx_id 在數組中,表示這個版本是由還沒提交的事務生成的,不可見。

②若 row trx_id 不在數組中,表示這個版本是已經提交了的事務生成的,可見。

總結:InnoDB 的行數據有多個版本,每一個數據版本有本身的 row trx_id,每一個事務或者語句有本身的一致性視圖。普通查詢語句是一致性讀,一致性讀會根據 row trx_id 和一致性視圖肯定數據版本的可見性。對於可重複讀,查詢只認可在事務啓動前就已經提交完成的數據,對於讀提交,查詢只認可在語句啓動前就已經提交完成的數據;而當前讀,老是讀取已經提交完成的最新版本。

2.4使用場景及優勢

場景

按照讀寫操做,咱們能夠將數據庫的併發場景分爲三種

  • 讀-讀

不會存在安全問題,因此不須要併發控制

  • 讀-寫

有線程安全問題,可能會形成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀

  • 寫-寫

有線程安全問題,可能會存在更新丟失問題。

優勢

  • 在併發讀寫數據庫時,能夠作到在讀操做時不用阻塞寫操做,寫操做也不用阻塞讀操做,提升了數據庫併發讀寫的性能
  • 同時還能夠解決髒讀,幻讀,不可重複讀等事務隔離問題,但不能解決更新丟失問題

在下面的文章中咱們就用實際案例來學習下一致性讀、當前讀

三、當前讀、一致性讀

3.1當前讀

當前讀能夠理解爲讀取的是記錄的最新版本。讀取時要保證其餘併發事務不能修改當前的記錄,如下語句是當前讀的

  • select lock in share mode(共享鎖)
  • select for update
  • update;insert ;delete

咱們一個案例來理解下當前讀

建立表並插入數據

mysql> CREATE TABLE `t` (
 `id` int(11) NOT NULL,  `k` int(11) DEFAULT NULL,  PRIMARY KEY (`id`) ) ENGINE=InnoDB; insert into t(id, k) values(1,1),(2,2); 複製代碼
事務A 事務B 事務C
start transaction with consisent snapshot
start transaction with consisent snapshot
update t set k=k+1 where id=1;
update t set k=k+1 where id=1;select k from t where id=1;
select k from t where id=1 lock in share mode; commit
commit

當前讀須要知足讀取的數據爲最新版本,咱們一次分析事務A、事務B、事務C讀取的數據

事務A

事務A在以共享鎖的形式讀取數據時,事務B更新了數據,可是沒有提交,因此對於事務A來講不可見;事務C在事務A真正的讀取數據以前,將k=k+1,即k=2,因此事務A讀取的數據k爲2

事務B

事務B在更改並查詢數據以前,事務C已經讀數據修改+1並提交了事務,因此事務B修改數據時會K=k+1=3,則事務B查詢的數據就爲3.

事務C

事務C是最早更改的數據並提交的,修改的數據k=k+1=2。

3.2一致性讀

一致性讀主要是針對普通的查詢語句的,因此將事務A中的的查詢語句換成正常的查詢語句,即select k from t where id=1

事務A 事務B 事務C
start transaction with consisent snapshot
start transaction with consisent snapshot
update t set k=k+1 where id=1;
update t set k=k+1 where id=1;select k from t where id=1;
select k from t where id=1 ; commit
commit

此時,由於事務B,事務C是當前讀,因此數據不會放生變化,依舊是事務C修改後K的值爲2,事務B修改並查詢獲得的K爲3。事務A由於從當前讀換成了一致性讀,咱們來具體分析下事務A。

事務A在開啓事務的瞬間肯定了事務ID,假設爲10,在事務A執行查詢語句,事務B、事務C依次開啓了事務,由於事務ID是單調遞增的,咱們能夠假設事務B的ID爲20,事務C的ID爲30,按照咱們MVCC,咱們把這些ID放入數組中

咱們能夠看到,在事務A的ID數組中只有事務A,這是由於事務B和事務C都是在在事務A以後開啓的,對事務A不可見,因此事務A讀取的數據爲1。

參考

[1]林曉斌.《MySQL實戰45講》

[2]https://www.cnblogs.com/kismetv/p/10331633.html

[3]https://www.jianshu.com/p/8845ddca3b23

歡迎關注公衆號:10分鐘編程,讓咱們天天博學一點點

公衆號回覆success領取獨家整理的學習資源

相關文章
相關標籤/搜索