mysql(三) mysql事務(InnoDB引擎時)實現原理

   這裏所說的MySQL事務是指使用InnoDB引擎時的事務。MySQL在5.5版本之前默認的數據庫引擎時MyISAM,雖然性能極佳,而且提供了大量的特性,包括全文索引、壓縮、空間函數等,但MyISAM不支持事務和行級鎖,而且最大的缺陷就是崩潰後無法安全恢復。5.5版本之後,MySQL引入了InnoDB(事務性數據庫引擎),MySQL 5.5版本後默認的存儲引擎爲InnoDB。

redo log和undo log來保證事務的原子性、一致性和持久性,同時採用預寫式日誌(WAL)方式將隨機寫入變成順序追加寫入,提升事務性能。而隔離性是通過鎖技術來保證的。

這裏我們不妨先來了解一下redo log和undo log。redo log是重做日誌,提供前滾操作,undo log是回滾日誌,提供回滾操作。undo log不是redo log的逆向過程,其實它們都算是用來恢復的日誌:

  • redo log通常是物理日誌,記錄的是數據頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣,它用來恢復提交後的物理數據頁(恢復數據頁,且只能恢復到最後一次提交的位置)。
  • undo用來回滾行記錄到某個版本。undo log一般是邏輯日誌,根據每行記錄進行記錄。

預寫式日誌:
事務日誌採用追加的方式,因此寫日誌的操作是磁盤上一小塊區域內的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭。事務日誌持久以後,內存中被修改的數據在後臺可以慢慢的刷回到磁盤。我們通常稱爲預寫式日誌,修改數據需要寫兩次磁盤。
如果數據的修改已經記錄到事務日誌並持久化,但數據本身還沒有寫回磁盤,此時系統崩潰,存儲引擎在重啓時能夠自動恢復這部分修改的數據。

(一)redo log:

redo log 又稱爲重做日誌,它包含兩部分:

  • 一是內存中的日誌緩衝(redo log buffer),該部分日誌是易失性的;
  • 二是磁盤上的重做日誌文件(redo log file),該部分日誌是持久的。

當需要修改事務中的數據時,先將對應的redo log寫入到redo log buffer中,然後纔在內存中執行相關的數據修改操作。InnoDB通過“force log at commit”機制實現事務的持久性,即在事務提交的時候,必須先將該事務的所有redo log都寫入到磁盤上的redo log file中,然後待事務的commit操作完成纔算整個事務操作完成。

在每次將redo log buffer中的內容寫入redo log file時,都需要調用一次fsync操作,以此確保redo log成功寫入到磁盤上(參考下圖,內容的流向爲:用戶態的內存->操作系統的頁緩存->物理磁盤)。因此,磁盤的性能在一定程度上也決定了事務提交的性能。這裏還可以通過innodb_flush_log_at_trx_commit來控制redo log刷磁盤的策略,這裏就不做贅述了。

在這裏插入圖片描述

(二)undo log:

   undo log有2個功能:實現回滾和多版本併發控制(MVCC, Multi-Version Concurrency Control)。

在數據修改的時候,不僅記錄了redo log,還記錄了相對應的undo log,如果因爲某些原因導致事務失敗或回滾了,可以藉助該undo log進行回滾。

undo log和redo log記錄物理日誌不一樣,它是邏輯日誌。可以認爲當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。

當執行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容並進行回滾。有時候應用到行版本控制的時候,也是通過undo log來實現的:當讀取的某一行被其他事務鎖定時,它可以從undo log中分析出該行記錄以前的數據是什麼,從而提供該行版本信息,讓用戶實現非鎖定一致性讀取。

(三)MVCC:

   說到undo log,就不得不順帶提一下MVCC了,因爲MVCC的實現依賴了undo log。當然,MVCC的實現還依賴了隱藏字段(DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID)、Read View等。

MVCC的全稱是多版本併發控制,它使得在使用READ COMMITTD、REPEATABLE READ這兩種隔離級別的事務下執行一致性讀操作有了保證。換言之,就是爲了查詢一些正在被另一個事務更新的行,並且可以看到它們被更新之前的值。這是一個可以用來增強併發性的強大技術,因爲這樣的一來的話查詢就不用等待另一個事務釋放鎖,使不同事務的讀-寫、寫-讀操作併發執行,從而提升系統性能。

這裏的讀指的是“快照讀”。普通的SELECT操作就是快照讀,有的地方也稱之爲“一致性讀”或者“一致性無鎖讀”。它不會對錶中的任何記錄做加鎖動作,即不加鎖的非阻塞讀。快照讀的前提是隔離級別不是串行化級別,串行化級別下的快照讀會退化成當前讀。之所以出現快照讀的情況,是基於提高併發性能的考慮,這裏可以認爲MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷。當然,既然是基於多版本,即快照讀可能讀到的並不一定是數據的最新版本,而有可能是之前的歷史版本。

對應的還有“當前讀”。類似UPDATE、DELETE、INSERT、SELECT…LOCK IN SHARE MODE、SELECT…FOR UPDATE這些操作就是當前讀。爲什麼叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他併發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

(四)鎖技術:

   併發事務的讀-讀情況並不會引起什麼問題(讀取操作本身不會對記錄有任何影響,並不會引起什麼問題,所以允許這種情況的發生),不過對於寫-寫、讀-寫或寫-讀這些情況可能會引起一些問題,需要使用MVCC或者加鎖的方式來解決它們。

在使用加鎖的方式解決問題時,既要允許讀-讀情況不受影響,又要使寫-寫、讀-寫或寫-讀情況中的操作相互阻塞。這裏引入了兩種行級鎖:

  • 共享鎖:英文名爲Shared Locks,簡稱S鎖。允許事務讀一行數據。
  • 排它鎖:也常稱獨佔鎖,英文名爲Exclusive Locks,簡稱X鎖。允許事務刪除或更新一行數據。

假如事務A首先獲取了一條記錄的S鎖之後,事務B接着也要訪問這條記錄:1) 如果事務B想要再獲取一個記錄的S鎖,那麼事務B也會獲得該鎖,也就意味着事務A和B在該記錄上同時持有S鎖;2) 如果事務B想要再獲取一個記錄的X鎖,那麼此操作會被阻塞,直到事務A提交之後將S鎖釋放掉。

如果事務A首先獲取了一條記錄的X鎖之後,那麼不管事務B接着想獲取該記錄的S鎖還是X鎖都會被阻塞,直到事務A提交。

除了 S鎖 和 S 鎖兼容,其他都不兼容。

InnoDB存儲引擎還支持多粒度鎖定,這種鎖定允許事務在行級上的鎖和表級上的鎖同時存在。爲此,InnoDB存儲引擎引入了意向鎖(表級別鎖):

  • 意向共享鎖(IS 鎖):事務想要獲取一張表的幾行數據的共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。
  • 意向排他鎖(IX 鎖):事務想要獲取一張表中幾行數據的排它鎖,事務在給一個數據行加排它鎖前必須先取得該表的 IX 鎖。

當我們在對使用InnoDB存儲引擎的表的某些記錄加S鎖之前,那就需要先在表級別加一個IS鎖,當我們在對使用InnoDB存儲引擎的表的某些記錄加X鎖之前,那就需要先在表級別加一個IX鎖。IS鎖和IX鎖的使命只是爲了後續在加表級別的S鎖和X鎖時判斷表中是否有已經被加鎖的記錄,以避免用遍歷的方式來查看錶中有沒有上鎖的記錄。

鎖粒度
MySQL中不同的存儲引擎支持不同的鎖機制:MyISAM與MEMORY存儲引擎採用表級鎖;BDB存儲引擎採用的是頁級鎖,也支持表級鎖;InnoDB存儲引擎既支持行級鎖,也支持表級鎖,默認採用行級鎖。
1:表級鎖: MySQL中開銷最小的策略,加鎖速度快,鎖定整張表,粒度大。不會出現死鎖,發生鎖競爭的概率最高,併發度最低,性能最差。
2:行級鎖: 開銷大,加鎖速度慢,鎖定一行數據,粒度小。會出現死鎖,發生鎖競爭的概率最低,併發讀最高,性能高。
3:頁級鎖: 開銷和加鎖速度介於表鎖和行鎖之間,鎖定一頁數據。會出現死鎖,鎖競爭概率、併發性、性能均位於表鎖和行鎖之間。

下表展示了X、IX、S、IS鎖的兼容性:
在這裏插入圖片描述

這裏還要了解一下的是,在InnoDB中有 3 種行鎖的算法:

  • Record Locks(記錄鎖):單個行記錄上的鎖。
  • Gap Locks(間隙鎖):在記錄之間加鎖,或者在第一個記錄之前加鎖,亦或者在最後一個記錄之後加鎖,即鎖定一個範圍,而非記錄本身。
  • Next-Key Locks:結合 Gap Locks 和 Record Locks,鎖定一個範圍,並且鎖定記錄本身。主要解決的是
    REPEATABLE READ 隔離級別下的幻讀問題。

對於Next-Key Locks,如果我們鎖定了一個行,且查詢的索引含有唯一屬性時(即有唯一索引),那麼這個時候InnoDB會將Next-Key Locks優化成Record Locks,也就是鎖定當前行,而不是鎖定當前行加一個範圍;如果我們使用的不是唯一索引鎖定一行數據,那麼此時InnoDB就會按照本來的規則鎖定一個範圍和記錄。還有需要注意的點是,當唯一索引由多個列組成時,如果查詢僅是查找其中的一個列,這時候是不會降級的。InnoDB存儲引擎還會對輔助索引的下一個鍵值區間加Gap Locks(這麼做也是爲了防止幻讀)。

(五)總結:

MySQL實現事務ACID特性的方式總結如下:

  • 原子性:使用 undo log來實現,如果事務執行過程中出錯或者用戶執行了rollback,系統通過undo
    log日誌返回事務開始的狀態。
  • 持久性:使用 redo log來實現,只要redo log日誌持久化了,當系統崩潰,即可通過redo log把數據恢復。
  • 隔離性:通過鎖以及MVCC來實現。
  • 一致性:通過回滾、恢復以及併發情況下的隔離性,從而實現一致性。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章