深刻分析Innodb的事務

首先,咱們都知道數據庫事務的四大特性:mysql

  • 原子性:事務是最小的工做單元,一個事務要麼所有成功,要麼所有失敗
  • 一致性:事務開始和結束以後,數據庫的完整性不會遭到破壞,也就保證事務是從一個正確的狀態變成另外一個正確狀態(正確狀態:咱們預期的狀態)
  • 隔離性:不一樣事務之間不會相互影響,隔離級別一共有四種,讀未提交,讀已提交,可重複讀,串行化
  • 持久性:事務提交以後,對數據的修改是永久的,即便系統出現故障也不會丟失

Innodb架構

上面是Innodb存儲引擎的架構,咱們能夠直觀的看到存儲引擎由內存池,後臺線程,和磁盤文件三大部分組成。

Buffer Pool緩衝池

用於處理數據,page是Innodb存儲的最基本結構,也是Innodb磁盤管理的最小單位,數據變動的時候,緩存裏的數據頁和磁盤的數據頁不一致,該數據頁被稱爲髒頁。sql

Redo Log Buffer 重作日誌緩衝

redo log保證數據的可靠性,存儲數據以前首先要存儲變動數據的日誌,一旦系統出現故障能夠從日誌中找。Innodb採用了Write Ahead Log(預寫日誌)策略,就是當事務提交時,先寫重作日誌,而後再擇時將髒頁落盤。Force Log at Commit機制保證事務的持久性,事務提交的時候,必須先將該事務的全部日誌記錄落盤,在每次將重作日誌緩衝寫入到重作日誌後,必須調用一次fsync操做,將緩衝日誌從文件系統緩存真正寫入磁盤數據庫

重作日誌的落盤機制

redo log落盤機制有三種,能夠經過參數innodb_flush_log_at_trx_commit進行設置,默認值爲1緩存

  • 0:事務提交的時候不進行重作日誌的寫入,並且每隔固定的時間寫入操做系統緩存並落盤
  • 1:事務提交的時候直接寫入到緩存並刷新到磁盤
  • 2:事務提交的時候先寫到操做系統的緩存,而後每隔固定的時間再將操做系統緩存中的數據刷新到磁盤

數據落盤機制

上圖是數據落盤和redo log日誌落盤的機制,咱們說下這個數據落盤時候的雙寫機制和檢查點bash

Double Write

Innodb經過雙寫機制保證數據的可靠性。Double Write由兩個部分構成,一個是內存中的double write buffer,大小爲2MB。第二部分是物理磁盤,共享表空間中的128個連續的頁,大小也是2MB。數據結構

double write 過程

在對緩衝池中的髒頁進行刷新的時候,並非直接將數據寫到磁盤。首先,經過mencpy函數將髒頁複製到內存中的double write buffer區,而後將double write buffer中的數據分兩次,每次1MB,將數據順序地寫入到共享表空間的磁盤上,而後立刻調用fsnyc真正地落盤。完成以後,再講double write buffer中的數據寫入到各個表空間中。若是操做系統在寫入磁盤的過程當中崩潰了,能夠從共享表空間中找到數據副本恢復數據。架構

checkpoint 檢查點

表示將髒頁寫入到磁盤的時機:併發

  • 目的:用於縮短數據庫的恢復時間,buffer pool空間不夠時,將數據刷新到磁盤,redo log不夠用的時候刷新髒頁
  • 分類
    • sharp checkpoint:徹底檢查點,數據庫正常關閉的時候,會觸發把全部的髒頁都寫入磁盤
    • fuzzy checkpoint:模糊檢查點,使用過程當中會觸發。
      • master thread checkpoint:固定時間將緩衝池中的髒頁按必定比例落盤,異步落盤
      • flush_lru_list checkpoint:讀取lru列表,將最近最少使用的數據刷新到磁盤
      • flush checkpoint:redo log file快滿了的時候回觸發批量的數據落盤,這個事件觸發的時候有兩種狀況,不可被覆蓋的redo log佔log file的比值爲75%觸發異步刷新,大於等於90%會觸發同步落盤。

undo log

當數據庫崩潰以後會利用redo log將尚未及時落盤的數據恢復,從新寫入磁盤。在恢復的過程當中還須要去回滾尚未提交的事務,回滾事務就須要利用到undo log,而undo log的完整性和可靠性須要redo log保證,所以恢復數據庫的時候,首先將redo log裏面數據恢復,而後作undo log的回滾。異步

undo log的存儲

數據和回滾日誌的每條記錄都會有三個額外的字段:函數

  • rowid:行id
  • Trx id:事務id
  • Roll Pointer:回滾指針,指向上一個歷史版本

undo log並無使用額外的文件存儲,而是存放在共享表空間的回滾段中。undo log的產生也能夠當作是數據庫的數據,所以,undo log 也會寫入到redo log中,也就是undo log 的產生會伴隨着redo log的產生,undo log的完整性和可靠性也是由redo log來保證

原子性,一致性,持久性原理分析

原子性,一致性和持久性主要是經過redo log,undo log和Force Log at commit機制來完成的。redo log用在數據庫崩潰的時候,從redo log恢復數據,undo log用於對事務的影響進行撤銷,也就是回滾,還用於多咱們後面會講到的多版本控制。Force log at commit保證事務提交以後可以持久化到redo log。

隔離性

事務的併發問題

  • 丟失更新:兩個事務針對同一個數據進行更新的時候,會存在丟失更新問題
  • 髒讀:一個事務讀取到另外一個事務未提交的數據
  • 不可重複度:一個事務對同一條記錄讀取兩個的結果不同
  • 幻讀:一個事務對同一張表讀取兩次結果不一致

針對上面的四個問題,事務之間有不一樣的隔離級去解決上面的問題

四種隔離級別

  • RU(讀未提交):最低的隔離級別,沒法解決上面的任何問題
  • RC(讀已提交):能夠避免髒讀的發送,只有提交的數據才能被讀取
  • RR(可重複讀):能夠避免髒讀和不可重複讀(Innodb中,RR還能夠用於解決幻讀,主要是經過Next——Key鎖實現,關於鎖的相關問題,能夠閱讀上一篇文章:juejin.im/post/5e1d6d…
  • Serializable(串行化):能夠避免髒讀,不可重複讀,幻讀的發生,但效率是最低的

Innodb的MVCC實現

在Innodb中,使用的是MVCC來保證事務之間的隔離,MVCC使得廣泛的select語句不會加鎖,提升數據庫的併發處理能力

什麼是MVCC

MVCC:多版本併發控制,是一種併發控制的方法,用來實現數據庫的事務性。

當前讀和快照讀

在MVCC中,讀操做能夠分紅兩種

  • 當前讀:讀取的是記錄的最新版本,而且讀到的記錄,都會加鎖,保證其餘事務不會再修改這條記錄
  • 快照讀:讀取的是記錄的可見版本,有可能讀的是歷史版本,不須要額外加鎖,就是select語句

在Innodb中簡單的select語句屬於快照讀,不加鎖,讀的是歷史版本。而其餘的增刪改語句屬於當前讀,須要加鎖,讀的是當前版本。

一致性非鎖定讀

一致性非鎖定讀:Innodb引擎經過MVCC讀取當前數據庫中行數據的方式。若是讀取的是正在執行刪除或者更新操做的記錄,那麼本次讀操做不會所以阻塞去等待鎖的釋放。而會去讀取該行的一個最新的可見快照。

MVCC的實現原理

MVCC的實現主要依賴的是undo log和read view(事務鏈表)

undo log

咱們知道undo log的行記錄中有三個隱藏字段:分別對應該行的rowid、事務號db_trx_id和回滾指針db_roll_ptr,其中db_trx_id表示最近修改的事務的id,db_roll_ptr指向回滾段中的undo log。 根據行爲的不一樣,undo log分爲兩種

  • insert undo log:insert中產生的undo log,insert操做只對當前事務可見,rollback在該事務中是直接刪除的,所以不須要進行purge操做(清除操做,經過purge Thread實現)
  • update undo log:更新或者刪除操做產生的undo log,這兩類操做會對已經存在的記錄產生影響,爲了被MVCC讀取,不能在事務提交的時候進行刪除,而是在事務提交以後放到歷史列表中,等待purge線程進行最後的刪除。

read view

事務鏈表,mysql中的事務在開始到提交的階段都會被保持在一個叫trx_sys的事務鏈表中啊,事務鏈表中保持的都是還未提交的事務,事務一旦被提交,就會從鏈表中刪除該事務。

  • RR隔離級別下,每一個事務開始的時候會將當期系統中全部活躍的事務拷貝到鏈表中
  • RC隔離級別下,每一個語句開始的時候,會將當前系統中的全部活躍的事務拷貝到一個鏈表中。

說道這裏,你們就應該清楚了爲何RR能夠避免不能夠重複讀而RC不行,由於二者獲取事務鏈表的方式不一樣,RC每一個語句都會從新獲取,所以能夠讀取到其餘事務最新提交的數據。 咱們能夠經過show engine innodb status語句查看事務鏈表

如何讀歷史版本

咱們知道事務開啓的時候會獲取事務鏈表,這個類中存儲了當前鏈表中最大的事務ID和最小的事務id。 假設當前活躍的事務鏈表以下:

ct-trx-->trx11 --> trx10 --> trx9 --> trx5 --> trx2;
複製代碼

ct-trx表示當前事務id,對應的read_view數據結構以下

read_view->creator_trx_id = ct-trx; 
read_view->up_limit_id = trx2; 低水位 
read_view->low_limit_id = trx11; 高水位 
read_view->trx_ids = [trx11, trx10, trx9, trx5, trx2];
複製代碼
  • low_limit_id:高水位,及當前活躍事務的最大id,若是讀到的row的事務id>=low_limit_id,說明這些id在此以前的數據都沒有提交,數據都不能夠見
  • up_limit_id:低水位,及最小的事務id,同理,若是讀到的row的事務id<=up_limit_id,說明這些數據在事務建立的時候id都已經提交了,數據都可見。
  • 當事務id在二者之間的時候,則查找該記錄的當前記錄的事務id是否在鏈表中,若是在說明,當前事務還未提交在當前版本中還未提交,當前版本不可見,若是不在,則數據可見。
相關文章
相關標籤/搜索