MySQL InnoDB技術內幕:內存管理、事務和鎖

前面有多篇文章介紹過MySQL InnoDB的相關知識,今天咱們要更深刻一些,看看它們的內部原理和機制是如何實現的。html

1、內存管理

咱們知道,MySQl是一個存儲系統,數據最後都寫在磁盤上。咱們之前也提到過,磁盤的速度特別是大容量的磁盤受磁頭臂的影響,速度相對內存慢不少。因此Innodb實現了本身的緩存機制。前端

首先咱們先看下Innodb對內存是如何使用和劃分的,而後咱們再看看它是如何保存熱數據的。算法

一、主要模塊和組成

(1) Buffer Pool

 預分配的內存池sql

(2) Page

 Buffer Pool的最小單位緩存

(3) Free list

 空閒Page組成的鏈表安全

(4) Flush list

髒頁鏈表併發

(5) Page hash 表

維護內存Page和文件Page的映射關係運維

(6) LRU

內存淘汰算法 post

以上三種鏈表LRU list、Free list、Flush list 和內存池、Page hash 以及磁盤文件之間的映射關係以下圖所示:      性能

 

二、LRU算法

 LRU,Least Recent Used,最近最少使用。每次將剛使用過的頁面插到LRU隊列的最前端,那麼最少使用的排在尾端,當緩存不夠時,淘汰尾端的頁。

不少文件系統和開源庫的內存淘汰算法都用到了LRU,之前有很多文章都提到過。

可是LRU的缺陷是,有時會沒法淘汰真正的冷數據,尾端的數據可能暫時沒使用而已,不表明不使用頻繁,不表明不是熱數據。因此不少系統對LRU進行了優化。

 好比Redis加了LFU(least frequently used最不常用)配合LRU一塊兒使用。

那麼InnoDB存儲引擎是如何改進的呢?以下圖,它將LRU分紅兩部分,中間的分割點叫作midpoint,新讀取的頁再也不是加入到最頭部,而是midpoint後面的位置,即後半截的頭部。

那麼midpoint的位置是如何計算的呢,在默認配置下,離LRU整個頭部的5/8處。固然這個比例是能夠根據實際業務進行設置的。但總之,能夠真正將冷熱數據分離,熱數據在前,冷數據在後。

 

那麼這兩個區的數據如何移動的呢,即冷熱數據如何切換的呢?

上面咱們提到了,剛插入的頁放在old區的頭部,那麼若是該頁確實訪問頻繁,不能一直呆在該位置吧。

InnoDB引入了參數innodb_old_blocks_time,若是old區的數據在該時間範圍內沒有被淘汰出去,就能夠移到new區,加入到new區的頭部。這也叫作made young

而若是在old呆的時間不夠innodb_old_blocks_time,並且緩存不夠時,就會面臨直接淘汰,這就叫作made not young。這種狀況,能夠發生在全表掃描的時候,保證了new區的數據纔是真正的熱數據!

固然數據也有可能從new區移動到old區,只是相對比較簡單了,直接移動midpoint指向的位置便可。即new區的尾巴變成了old區的頭部。 

2、事務

一、MySQL事務基本概念

事務特性

A(Atomicity原子性):所有成功或所有失敗

I(Isolation隔離性):並行事務之間互不干擾

D(Durability持久性):事務提交後,永久生效

C(Consistency一致性):經過AID保證


併發問題

髒讀(Drity Read):讀取到未提交的數據。中間全部變化的值均可能讀到

不可重複讀(Non-repeatable read):兩次讀取結果不一樣。讀取已提交的(不同的值),讀到的值變化數量比髒讀要少

幻讀(Phantom Read):select 操做獲得的結果所表徵的數據狀態影響(沒法支撐)後續的業務操做。

網上有人這樣區分,髒讀是讀取修改的數據,幻讀是讀取新提交的數據。我認爲也能夠,或許phantom表示虛幻的新數據(因此沒法支撐後續操做),而drity表明了修改的意思呢?

因此,不可重複讀重點在於update和delete,而幻讀的重點在於insert。


隔離級別

Read Uncommitted(讀取未提交內容):最低隔離級別,會讀取到其餘事務未提交的數據;存在髒讀的問題

Read Committed(讀取提交內容):事務過程當中能夠讀取到其餘事務已提交的數據;存在不可重複讀的問題

Repeatable Read(可重複讀):每次讀取相同結果集,無論其餘事務是否提交;存在幻讀的問題

Serializable(串行化):事務排隊,隔離級別最高,性能最差。

 

二、MySQL事務實現原理

從上咱們能夠看出事務有ACID四大特性,而「I」隔離性是經過鎖來實現的,咱們下一節講述。那麼其餘三個特性主要經過undo/redo日誌的機制來實現,這個知識點在前面有一篇文章中介紹和對比過。如今咱們站在事務實現的角度再來看看。

(1)undo log

回滾日誌,顧名思義,是對事務rollback時使用。這是它核心的功能之一,可是它還有另外一個很是重要的功能,MVCC。因此今天這裏主要介紹它是如何在事務中發揮做用的。

  MVCC 

Multiversion concurrency control,多版本併發控制。當用戶讀取一行時,若是該記錄已經被其餘事務佔用,當前事務能夠經過undo讀取以前的行版本信息快照數據),以此實現非鎖定讀因此實現了非阻塞的讀操做,寫操做也只鎖定必要的行。即解決讀-寫衝突。

快照數據就是當前行數據的歷史版本,每行記錄可能含有多個版本。那該讀取哪一個版本呢?

首先,InnoDB的每行記錄或者說每條數據,除了記錄用戶定義的列以外,還有兩個隱藏的列:事務ID列DB_TRX_ID和回滾指針DB_ROLL_PTR。若是該表沒有定義主鍵,每行還會增長一個rowid列。DB_TRX_ID是當時執行這條sql的事務id,DB_ROLL_PTR指向的就是undo log中修改前的行DB_ROW_ID。因此對同一條數據的修改,經過roll_pointer就造成了undo log版本鏈

而後咱們再來介紹下Read View快照讀。

通常狀況下讀取數據時會生成一個Read View,對當前該行的可能正在進行的事務進行一個快照。

Read View中主要包含4個比較重要的內容:

  • m_ids:表示在生成Read View時當前系統中活躍的讀寫事務的事務id列表,簡稱活躍列表

  • min_trx_id:表示在生成Read View時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小值。

  • max_trx_id:表示生成Read View時系統中應該分配給下一個事務的id值,,也就是m_ids中的最大值。

  • creator_trx_id:表示生成該Read View的事務的事務id。

 

有了這些信息,這樣在訪問某條記錄時,只須要依次判斷undo log版本鏈中節點的事務ID是否可見,若是可見即找到了所須要的行記錄。

  

最後,READ COMMITTED和REPEATABLE READ兩種隔離級別對於快照數據生成的時機不同。

對於RC,在每次查詢語句執行的過程當中,都關閉Read View, 再建立當前的一份Read View。這樣就會產生不可重複讀現象。

對於RR,建立事務trx結構的時候,就生成了當前的global Read View,一直維持到事務結束。在事務結束這段時間內每一次查詢都不會從新重建Read View,從而實現了可重複讀。

 

  undo log分爲兩種格式,處理不同。

insert undo log:用於回滾,提交即清理;不須要進行purge操做。

update undo log:用於回滾,同時實現快照讀,不能隨便刪除,因此須要等待purge線程來判斷什麼時候刪除。它記錄的是對delete和update的操做產生的undo log。

注:以上來自書上的說法,網上有人把第一種說成delete undo log,包括insert和delete操做,供參考。 

還須要補充一點的是,update undo log怎樣去清理,應該是根據系統活躍的Read view中最小的活躍事務ID以前的便可清除。

(2)redo log

redo日誌其實在《MySQL的undo/redo日誌和binlog日誌,以及2PC》文章中介紹比較多,也提到了XA事務的2PC。咱們這裏簡單介紹下普通事務的流程。

 

寫入流程仍然能夠分爲兩步,相似二階段提交:

  • 記錄頁的修改,狀態爲prepare

  • 事務提交,講事務記錄爲commit狀態

3、鎖

一、InnoDB鎖種類

(1) 類型

共享鎖(S)

  讀鎖,能夠同時被多個事務獲取,阻止其餘事務對記錄的修改。

排他鎖(X)

  寫鎖,只能被一個事務獲取,容許得到鎖的事務修改數據。    

而讀其實又能夠分爲當前讀(鎖定讀)和快照讀(非鎖定讀),而快照讀經過上一節描述的MVCC來實現。

當前讀, 讀取的是最新版本,因此須要對讀取的記錄加鎖,阻塞其餘事務改動該記錄。當前讀又分爲兩種方式:

select...for update,對讀取的行加X鎖;

select...lock in share mode,對讀取的行加S鎖。  

(2)鎖粒度

行級鎖

Record Lock,單個記錄上的鎖。

鎖直接加在索引記錄上面,鎖住的是key,因此必須是聚簇索引或者二級索引是惟一索引。


間隙鎖

Gap Lock,間隙鎖,鎖定一個範圍,但不包含記錄自己。

InnoDB存儲引擎的隔離級別默認是Repeatable Read,因此引入了間隙鎖解決可重複讀模式下的幻讀問題

GAP鎖不是加在記錄上,鎖住的位置是兩條記錄之間的GAP;保證兩次當前讀返回一致的記錄。 

因此兩次當前讀以前,其餘的事務不會插入新的知足條件的記錄。

 

咱們來整理下着二者的關係和區別。

Record Lock針對的是索引必須具有惟一性;而GAP鎖針對的是索引不具有惟一性但須要保證可重複讀,也就是說若是發現數據有被其餘事務修改的可能,那就把先後間隙都加上鎖。

好比說以下圖,有個用戶表,uid爲主鍵,那麼就只須要103這條記錄加上行鎖便可。

  

可是若是咱們變化下查詢條件(phone列上創建了二級索引),則除了對於這兩條記錄加鎖外,對先後的間隙也須要加鎖。固然這種狀況是針對RR的隔離級別,若是隔離級別是RC或者更低,安全性就沒有這麼高,系統會自動降級到行鎖。

 

Next-Key Lock,是Record Lock與Gap Lock的一個結合。理解了上述兩種鎖的原理,對於它而言就很容易了。


表級鎖

Table Lock,鎖定整張表。

主要用在運維的時候,對錶格進行操做好比MDL或者元數據的操做(meta data lock)等等。

固然有些狀況下會觸發鎖升級:全表掃描。全表掃描的觸發通常狀況下是當前被查詢的字段沒有創建任何索引。

而表級鎖事實上是對全部記錄和全部的間隙都加上鎖。

因此全表掃描的效率很是低,要儘可能避免。

 

二、InnoDB加鎖過程

以下圖,當咱們更新多條數據時,是一行一行的加鎖。

 

因此當同時出現對多條記錄交叉查詢時,很容易出現AB-BA死鎖,以下圖操做。

 

 

附錄

分庫分表的建議:

是否分表
  建議單表不超過1KW

分表方式
  取模:存儲均勻&訪問均勻
  按時間:冷熱庫

分庫
  按業務垂直分
  水平查分多個庫

 

參考:

《MySQL技術內幕InnoDB存儲引擎》

內部培訓資料

相關文章
相關標籤/搜索