天天看點

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

MySQL事務

  • 系列文章
  • 一、什麼是事務
  • 二、事務四大特征
    • 1. 原子性
    • 2. 一緻性
    • 3. 隔離性
    • 4. 持久性
  • 三、事務并發會出現的問題
    • 1. 髒讀
    • 2. 不可重複讀
    • 3. 幻讀
    • 4. 差別
  • 四、事務日志以及事務異常如何應對
    • 1. 重做日志(redo log)(持久性實作原理)
    • 2. 伺服器異常停止對事務如何應對(事務寫入過程)
    • 3. 復原日志(undo log)(原子性實作原理)
  • 五、鎖機制
    • 1. 行鎖
    • 2. 表鎖
    • 3. 如何加鎖
  • 六、總結

關于學習這件事情甯可花點時間系統學習,也不要東一榔頭西一棒槌,都說學習最好的方式就是系統的學習,希望看完本文會讓你對事務有一定的了解。

資料庫版本為8.0

系列文章

1. 揭開MySQL索引神秘面紗

2. MySQL查詢優化必備

3. 上來就問MySQL事務,瑟瑟發抖...

一、什麼是事務

事務是獨立的工作單元,在這個獨立工作單元中所有操作要麼全部成功,要麼全部失敗。

也就是說如果有任何一條語句因為崩潰或者其它原因導緻執行失敗,那麼未執行的語句都不會再執行,已經執行的語句會進行復原操作,這個過程被稱之為事務。

例:

最近在寫一個論壇系統,當釋出的主題被其它使用者舉報後,背景會對舉報内容進行稽核。

一經稽核為違規主題,則進行删除主題的操作,但不僅僅要删除主題還要删除主題下的文章、浏覽量,關于這個主題的一切資訊都需要進行清理。

删除流程如下,用上邊概念來說,以下執行的四個流程,每個流程都必須成功否則事務復原傳回删除失敗。

假設執行到了第三步後SQL執行失敗了,那麼第一二步都會進行復原,第四步則不會在執行。

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

二、事務四大特征

事務的四大特征,原子性、一緻性、隔離性、持久性。

1. 原子性

事務中所有操作要麼全部成功,要麼全部失敗,不會存在一部分成功,一部分失敗。

這個概念也是事務

最核心

的特性,事務概念本身就是使用原子性進行定義的。

原子性的實作是基于復原日志實作(undo log),當事務需要復原時就會調用復原日志進行SQL語句復原操作,實作資料還原。

2. 一緻性

一緻性,字面意思就是前後一緻呗!在資料庫中不管進行任何操作,都是從一個一緻性轉移到另一個一緻性。

當事務結束後,資料庫的完整性限制不被破壞。

當你了解完事務的四大特征之後就會發現,都是保證資料一緻性為最終目标存在的。

在學習事務的過程中大家看到最多的案例就是轉賬,假設使用者A與使用者B餘額共計1000,那麼不管怎麼轉倆人的餘額自始至終也就隻有1000。

3. 隔離性

保證事務執行盡可能的不受其它事務影響,這個是隔離級别可以自行設定,在innodb中預設的隔離級别為可重複讀(Repeatable Read)。

這種隔離級别有可能造成的問題就是出現幻讀,但是使用間隙鎖可以解決幻讀問題。

學習了隔離性你需要知道

原子性和持久性是針對單個事務

,而隔離性是針對事務與事務之間的關系。

4. 持久性

持久性是指當事務送出之後,資料的狀态就是永久的,不會因為系統崩潰而丢失。

事務持久性是基于重做日志(redo log)實作的。

三、事務并發會出現的問題

1. 髒讀

讀取了另一個事務沒有送出的資料。

事務A 事務B
執行事務 執行事務
主題通路量從100修改到150
查詢主題通路量為150
送出事務

以上表為例,事務A讀取主題通路量時讀取到了事務B沒有送出的資料150。

如果事務B失敗進行復原,那麼修改後的值還是會回到100。

然而事務A擷取的資料是修改後的資料,這就有問題了。

2. 不可重複讀

事務讀取同一個資料,傳回結果先後不一緻問題。

事務A 事務B
執行事務 執行事務
查詢主題通路量為100
修改主題通路量為200
送出事務
查詢主題通路量為200

上表格中,事務A在先後擷取主題通路量時,傳回的資料不一緻。

也就是說在事務A執行的過程中,通路量被其它事務修改,那麼事務A查詢到的結果就是不可靠的。

**髒讀與不可重複讀的差別**

髒讀讀取的是另一個事務沒有送出的資料,而不可重複讀讀取的是另一個事務已經送出的資料。

3. 幻讀

事務按照

範圍

查詢,倆次傳回結果不同。

事務A 事務B
開始事務 開始事務
查詢通路量100-200的主題個數為100
此時有一篇新的文章通路量達到了150
送出事務
再次查詢通路量100-200的主題個數為101

以上表為例,當對100-200通路量的主題做統計時,第一次找到了100個,第二次找到了101個。

4. 差別

  • 髒讀讀取的是另一個事務沒有送出的資料,而不可重複讀讀取的是另一個事務已經送出的資料。
  • 幻讀和不可重複讀都是讀取了另一條已經送出的事務(這點與髒讀不同),所不同的是不可重複讀查詢的都是同一個資料項,而幻讀針對的是一批資料整體(比如資料的個數)。

針對以上的三個問題,産生了四種隔離級别。

在第二節中對隔離性進行了簡單的概念解釋,實際上的隔離性是很複雜的。

在MySQL中定義了四種隔離級别,分别為未送出讀 (Read Uncommitted)、送出讀 (Read committed)、可重複讀取 (Repeatable Read)、可串行化 (Serializable)。

  • 未送出讀 (Read Uncommitted):倆個事務同時運作,有一個事務修改了資料,但未送出,另一個事務是可以讀取到沒有送出的資料。這種情況被稱之為

    髒讀

  • 送出讀(Read committed):一個事務在未送出之前,所做的任何操作其它事務不可見。這種隔離級别也被稱之為

    不可重複讀

    。因為會存在倆次同樣的查詢,傳回的資料可能會得到不一樣的結果。
  • 可重複讀(Repeatable Read):這種隔離級别

    解決了髒讀

    問題,但是還是

    存在幻讀

    問題,這種隔離界别在MySQL的innodb引擎中是預設級别。MySQL在解決幻讀問題使用間隙鎖來解決幻讀問題。
  • 可串行化 (Serializable):這種級别是最高的,強制事務進行串行執行,解決了可重複讀的幻讀問題。
隔離級别 髒讀 不可重讀讀 幻讀
未送出讀 (Read Uncommitted) 可能發生 可能發生 可能發生
送出讀(Read committed) 不可能發生 可能發生 可能發生
可重複讀(Repeatable Read) 不可能發生 不可能發生 可能發生
可串行化 (Serializable) 不可能發生 不可能發生 不可能發生

對于隔離級别,級别越高并發就越低,而級别越低會引發髒讀、不可重複讀、幻讀的問題。

是以在MySQL中使用

可重複讀(Repeatable Read)

作為預設級别。

作為預設級别是如何解決并處理相應問題的呢!

那麼針對這一問題,是一個難啃的骨頭,咔咔将在下一期MVCC文章專門來介紹這塊。

四、事務日志以及事務異常如何應對

MySQL的版本号為8.0

在Innodb中事務的日志分為倆種,復原日志、重做日志。

先來看一下倆個日志的存放位置吧!

在Linux下的MySQL事務日志存放在

/var/lib/mysql

這個位置中。

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

從上圖中可以看到分别為ib_logfile、undo_倆個檔案。

ib_logfile檔案為重做日志

undo_檔案為復原日志

在這裡估計有點小夥伴會有點迷糊這個復原日志。

那是因為在MySQL5.6預設復原日志沒有進行獨立表空間存儲,而是存放到了ibdata檔案中。

獨立表空間存儲從MySQL5.6後就已經支援了,但是需要自行配置。

在MySQL8.0是由

innodb_undo_tablespaces

這個參數來設定復原日志獨立空間個數,這個參數的範圍為0-128。

預設值為0表示不開啟獨立的復原日志,且復原日志存儲在ibdata檔案中。

這個參數是在初始化資料庫時指定的,執行個體一旦建立這個參數是不能改動的。

如果設定的innodb_undo_tablespaces 值大于執行個體建立時的個數,則會啟動失敗。

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

1. 重做日志(redo log)(持久性實作原理)

事務的持久性就是通過重做日志來實作的。

當送出事務之後,并不是直接修改資料庫的資料的,而是先保證将相關的操作記錄到redo日志中。

資料庫會根據相應的機制将記憶體的中的髒頁資料重新整理到磁盤中。

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

上圖是一個簡單的重做日志寫入流程。

在上圖中提到倆個陌生概念,Buffer pool、redo log buffer,這個倆個都是Innodb存儲引擎的記憶體區域的一部分。

而redo log file是位于磁盤位置。

也就說當有DML(insert、update、delete)操作時,資料會先寫入Buffer pool,然後在寫到重做日志緩沖區。

重做日志緩沖區會根據刷盤機制來進行寫入重做日志中。

這個機制的設定參數為

innodb_flush_log_at_trx_commit

,參數分别為0,1,2

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

上圖即為重做日志的寫入政策。

  • 當這個參數的值為0的時,送出事務之後,會把資料存放到redo log buffer中,然後每秒将資料寫進磁盤檔案
  • 當這個參數的值為1的時,送出事務之後,就必須把redo log buffer從記憶體刷入到磁盤檔案裡去,隻要事務送出成功,那麼redo log就必然在磁盤裡了。
  • 當這個參數的值為2的情況,送出事務之後,把redo log buffer日志寫入磁盤檔案對應的os cache緩存裡去,而不是直接進入磁盤檔案,1秒後才會把os cache裡的資料寫入到磁盤檔案裡去。

2. 伺服器異常停止對事務如何應對(事務寫入過程)

  • 當參數為0時,前一秒的日志都儲存在日志緩沖區,也就是記憶體上,如果機器宕掉,可能丢失1秒的事務資料。
  • 當參數為1時,資料庫對IO的要求就非常高了,如果底層的硬體提供的IOPS比較差,那麼MySQL資料庫的并發很快就會由于硬體IO的問題而無法提升。
  • 當參數為2時,資料是直接寫進了os cache緩存,這部分屬于作業系統部分,如果作業系統部分損壞或者斷電的情況會丢失1秒内的事務資料,這種政策相對于第一種就安全了很多,并且對IO要求也沒有那麼高。

小結

關于性能:0>2>1

關于安全:1>2>0

根據以上結論,是以說在MySQL資料庫中,刷盤政策預設值為1,保證事務送出之後,資料絕對不會丢失。

3. 復原日志(undo log)(原子性實作原理)

復原日志保證了事務的原子性。

復原日志相對重做日志來說沒有那麼複雜的流程。

當事務對資料庫進行修改時,Innodb引擎不僅會記錄redo log日志,還會記錄undo log日志。

如果事務失敗,或者執行了rollback,為了保證事務的原子性,就必須利用undo log日志來進行復原操作。

復原日志的存儲形式如下。

在undo log日志檔案,事務中使用的每條insert都對應了一條delete,每條update也都對應一條相反的update語句

上來就問MySQL事務,瑟瑟發抖...系列文章一、什麼是事務二、事務四大特征三、事務并發會出現的問題四、事務日志以及事務異常如何應對五、鎖機制六、總結

注意:

系統發生當機或者資料庫程序直接被殺死。

當使用者再次啟動資料庫程序時,還能夠立刻通過查詢復原日志将之前未完成的事務程序復原。

這也就需要復原日志必須先于資料持久化到磁盤上,是需要先寫日志後寫資料庫的主要原因。

復原日志不僅僅可以保證事務的原子性,還是實作mvcc的重要因素。

以上就是關于事務的倆大日志,重做日志、復原日志的了解。

五、鎖機制

鎖在MySQL中是是非常重要的一部分,鎖對MySQL資料通路并發有着舉足輕重的作用。

是以說鎖的内容以及細節是十分繁瑣的,本節隻是對Innodb鎖的一個大概整理。

MySQL中有三類鎖,分别為行鎖、表鎖、頁鎖。

首先需要明确的是這三類鎖是是歸屬于那種存儲引擎的。

  • 行鎖:Innodb存儲引擎
  • 表鎖:Myisam、MEMORY存儲引擎
  • 頁鎖:BDB存儲引擎

1. 行鎖

行鎖又分為共享鎖、排它鎖,也被稱之為讀鎖、寫鎖,Innodb存儲引擎的預設鎖。

共享鎖(S):

假設一個事務對資料A加了共享鎖(S),則這個事務隻能讀A的資料。

其它事務隻能再對資料A添加共享鎖(S),而不能添加排它鎖(X),直到這個事務釋放了資料A的共享鎖(S)。

這就保證了其它事務也可以讀取A的資料,但是在這個事務沒有釋放在A資料上的共享鎖(S)之前不能對A做任何修改。

排它鎖(X)

假設一個事務對資料A添加了排它鎖(X),則隻允許這個事務讀取和修改資料A。

其它任何事務都不能在對資料A添加任何類型的鎖,直至這個事務釋放了資料A上的鎖。

排它鎖阻止其它事務擷取相同資料的共享鎖(S)、排它鎖(X),直至釋放排它鎖(X)。

特點

  • 隻針對單一資料進行加鎖
  • 開銷大
  • 加鎖慢
  • 會出現死鎖
  • 鎖粒度最小,發生鎖沖突的機率越低,并發越高。

還記得在上文中提到的事務并發帶來的問題、髒讀、不可重讀讀、幻讀。

學習到了這裡,應該就明白

可重複讀(Repeatable Read)

如何解決髒讀、不可重讀讀了。

髒讀、和不可重複讀的解決方案很簡單,寫前加排它鎖(X),事務結束才釋放,讀前加共享鎖(S),事務結束就釋放

2. 表鎖

表鎖又分為表共享讀鎖、表獨占寫鎖,也被稱之為讀鎖、寫鎖,Myisa存儲引擎的預設鎖。

  • 表共享讀鎖 : 針對同一個份資料,可以同時讀取互不影響,但不允許寫操作。
  • 表獨占寫鎖 :當寫操作沒有結束時,會阻塞所有讀和寫。

特點

  • 對整張表加鎖
  • 開銷小
  • 加鎖快
  • 無死鎖
  • 鎖粒度最大,發生鎖沖突的機率越大,并發越小。

本文主要說明Innodb和Myisam的鎖,頁鎖不就不做詳細說明了。

3. 如何加鎖

表鎖

  • 隐式加鎖:預設自動加鎖釋放鎖,select加讀鎖、update、insert、delete加寫鎖。
  • 手動加鎖:lock table tableName read;(添加讀鎖)、lock table tableName write(添加寫鎖)。
  • 手動解鎖:unlock table tableName(釋放單表)、unlock table(釋放所有表)

行鎖

  • 隐式加鎖:預設自動加鎖釋放鎖,隻有select不會加鎖,update、insert、delete加排它鎖。
  • 手動加共享鎖:select id name from user lock in share mode;
  • 手動加排它鎖:select id name form user for update;
  • 解鎖:正常送出事務(commit)、事務復原(rollback)、kill程序。

六、總結

本文主要對事務的重點知識點進行解讀,内容總結。

事務四大特征實作原理

  • 原子性:使用事務日志的復原日志(undo log)實作
  • 隔離性:使用mvcc實作(幻讀問題除外)
  • 持久性:使用事務日志的重做日志(redo log)實作
  • 一緻性:是事務追求的最終目标,原子性、隔離性、持久性都是為了保證資料庫一緻性而存在

事務并發出現問題的差別

  • 髒讀與不可重複讀的差別:髒讀是讀取沒有送出事務的資料、不可重複讀讀取的是已送出事務的資料。
  • 幻讀與不可重複讀的差別:都是讀取的已送出事務的資料(與髒讀不同),幻讀針對的是一批資料,例如個數。不可重複讀針對的是單一資料。

事務日志

  • 重做日志(redo log):實作了事務的持久性,送出事務後不是直接修改資料庫,而是保證每次事務操作讀寫入redo log中。并且落盤會有三種政策(詳細看四-1節)。
  • 復原日志(undo log):實作了事務的原子性,針對DML的操作,都會有記錄相反的DML操作。
堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。希望在偌大網際網路中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。

繼續閱讀