天天看點

MySQL InnoDB 存儲引擎探秘

在MySQL中InnoDB屬于存儲引擎層,并以插件的形式內建在資料庫中。從MySQL5.5.8開始,InnoDB成為其預設的存儲引擎。InnoDB存儲引擎支援事務、其設計目标主要是面向OLTP的應用,主要特點有:支援事務、行鎖設計支援高并發、外鍵支援、自動崩潰恢複、聚簇索引的方式組織表結構等。

體系架構

InnoDB存儲引擎是由記憶體池、背景線程、磁盤存儲三大部分組成。

MySQL InnoDB 存儲引擎探秘

線程

InnoDB 使用的是多線程模型, 其背景有多個不同的線程負責處理不同的任務。

Master Thread

Master Thread是最核心的一個背景線程,主要負責将緩沖池中的資料異步重新整理到磁盤,保證資料的一緻性。包括髒頁重新整理、合并插入緩沖、UNDO頁的回收等。

IO Thread

在 InnoDB 存儲引擎中大量使用了異步IO(Async IO)來處理寫IO請求, IO Thread的工作主要是負責這些 IO 請求的回調。

Purge Thread

事務送出後,其所使用的undo log可能不再需要,是以需要Purge Thread來回收已經配置設定并使用的UNDO頁。InnoDB支援多個Purge Thread, 這樣做可以加快UNDO頁的回收,提高CPU的使用率以及提升存儲引擎的性能。

Page Cleaner Thread

Page Cleaner Thread的作用是取代Master Thread中髒頁重新整理的操作,其目的是減輕原Master Thread的工作及對于使用者查詢線程的阻塞,進一步提高InnoDB存儲引擎的性能。

記憶體

InnoDB 存儲引擎記憶體的結構

MySQL InnoDB 存儲引擎探秘

緩沖池

InnoDB存儲引擎是基于磁盤存儲的,并将其中的記錄按照頁的方式進行管理。但是由于CPU速度和磁盤速度之間的鴻溝,基于磁盤的資料庫系統通常使用緩沖池記錄來提高資料庫的的整體性能。

緩沖池其實就是通過記憶體的速度來彌補磁盤速度較慢對資料庫性能的影響。在資料庫進行讀取操作時,首先将磁盤中的頁放入緩沖池中,下次再讀取相同頁時,首先從緩沖池中擷取該頁資料,起到高速緩存的作用。

資料的修改操作,則首先修改在緩沖池中的頁資料,然後使用一種稱為Checkpoint的機制重新整理到磁盤上。

緩沖池的大小直接影響資料庫的整體性能,對于InnoDB存儲引擎而言,緩沖池配置通過參數 innodb_buffer_pool_size 來設定。使用 SHOW VARIABLES LIKE 'innodb_buffer_pool_size' 指令可檢視緩沖池配置:

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size' \G
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_size        
        Value: 134217728
1 row in set (0.01 sec)      

緩沖池中緩存的資料頁類型有: 索引頁、undo頁、插入緩沖、自适應哈希索引、InnoDB鎖資訊、資料字典資訊等,索引頁和資料頁占緩沖池很大的一部分。

重做日志緩沖

緩沖池中的頁資料比磁盤要新時,需要将新資料重新整理到磁盤中。InnoDB采用Write Ahead Log政策來重新整理資料,即當事務送出時,先寫入重做日志緩沖,重做日志緩沖會按一定頻率重新整理到重置日志檔案中,然後髒頁會根據checkpoint機制重新整理到磁盤。

重做日志緩沖不需要設定很大,通常情況下8M能滿足大部分的應用場景。重做日志支援一下三種情況觸發重新整理:

  • Master Thread每一秒将重做日志緩沖重新整理到重做日志檔案
  • 每次事務送出時将重做日志緩沖重新整理到重做日志檔案
  • 當重做日志緩沖池剩餘空間小于1/2時,重做日志緩沖重新整理到重做日志檔案
MySQL InnoDB 存儲引擎探秘

額外記憶體池

在InnoDB存儲引擎中,對記憶體的管理是通過一種稱為記憶體堆的方式進行的。在對一些資料結構本身的記憶體進行配置設定時,需要從額外的記憶體池中進行申請,當該區域的記憶體不夠時,會從緩沖池中進行申請。

InnoDB支援的鎖有:

  • 共享鎖和排它鎖
  • 意向鎖
  • 記錄鎖
  • 間隙鎖
  • 自增鎖

共享鎖和排他鎖

InnoDB引擎實作了兩種标準的行級鎖,共享鎖(shared (S) locks) 和排他鎖 (exclusive (X) locks)。共享鎖允許一個占有鎖的事務去讀取一行資料,排它鎖則允許事務對某一行記錄進行寫操作。

如果一個事務持有了一個共享鎖,其他事務仍然可以擷取這行記錄的共享鎖,但不能擷取到這行記錄的排它鎖。當一個事務擷取到了某一行的排它鎖,則其他事務将無法再擷取這行記錄的共享鎖和排它鎖。

在InnoDB中,意向鎖是一種表級鎖,分為共享鎖和排他鎖:

  • 意向共享鎖:将要去擷取某一行的共享鎖
  • 意向排它鎖:将要去擷取某一行的排它鎖

事務在擷取共享/排它鎖之前必須先擷取意向共享/排它鎖,意向鎖不會阻塞其他任何對表的操作,他隻是告訴其他事務他将要去擷取某一行的共享鎖或者排他鎖。

記錄是是作用在索引上的一種鎖,他鎖住的是某一條記錄的索引而非記錄本身,如果目前表沒有索引那麼InnoDB将會為其建立一個隐藏的聚集索引,而Record Locks将會鎖住這個隐藏的聚集索引。

間隙鎖和記錄鎖一樣也是作用在索引上,不同的是記錄鎖隻作用于一條索引記錄而間隙鎖可以鎖住一個範圍内的索引。間隙鎖在InnoDB的唯一作用就是防止其他事務的插入操作,以此防止幻讀的發生。

自增鎖是一種特殊的表級鎖,他隻作用在包含自增列的插入操作時。當一個事務正在插入一條資料時,其他的任何事務都必須等待整個事務完成插入操作,在取擷取鎖來執行插入操作。

事務

ACID

事務是資料庫作為OLTP最為重要的特性,說起事務不得不提起ACID四個基本特性:

  • 原子性(Atomicity) :事務最小工作單元,要麼全成功,要麼全失敗
  • 一緻性(Consistency): 事務開始和結束後,資料庫的完整性不會被破壞
  • 隔離性(Isolation) :不同僚務之間互不影響,四種隔離級别為RU(讀未送出)、RC(讀已送出)、RR(可重複讀)、SERIALIZABLE (串行化)
  • 持久性(Durability) :事務送出後,對資料的修改是永久性的,即使系統故障也不會丢失

InnoDB的原子性、持久性和一緻性主要是通過Redo Log、Undo Log和Force Log at Commit機制機制來完成的。Redo Log用于在崩潰時恢複資料,Undo Log用于對事務的影響進行撤銷,也可以用于多版本控制。而Force Log at Commit機制保證事務送出後Redo Log日志都已經持久化。隔離性則是由鎖和MVCC來保證的。

隔離級别

在MySQL中,事務有4種隔離級别,分别是:

  • Read Uncommitted 未送出讀
  • Read Committed 已送出讀
  • Repeatable Read 可重複讀
  • Serializable 可串行化

在了解四種隔離級别之前,我們需要先了解另外三個名詞:

  • 髒讀

    a事務會讀取到b事務還未送出的資料,但是b事務由于某種原因進行復原操作,這樣,a事務讀取的資料是不可用的,進而會造成一些異常結果。

  • 不可重複讀

    a事務周期内對某一資料多次查詢,同時這些資料在b事務中進行了update或delete操作。那麼a事務每次查詢出來的結果可能都不一樣。

  • 幻讀

    幻讀的結果其實和不可重複讀是一樣的表現,差異就在于不可重複讀主要是針對其他事務進行了編輯(update)和删除(delete)操作。而幻讀主要是針對插入(insert)操作。也就是在一個事務生命周期内,會查詢到另外一個事務新插入的資料。

Read uncommitted 未送出讀

未送出讀,這種情況下,一個事務a可以看到另一個事務b未送出的資料,如果此時事務b發生復原,那麼事務a拿到的就是髒資料,這也就是髒讀的含義。

此隔離級别在MySQL InnoDB一般不推薦使用。

已送出讀,一個事務從開始直到送出之前,所做的任何修改對其他事務都是不可見的。解決了髒讀問題,但是存在幻讀現象。

可重複讀,該級别保證在同一事務中多次讀取同樣記錄的結果是一緻的,在InnoDB存儲引擎中同時解決了幻讀和不可重複讀問題。

InnoDB引擎通過使用Next-Key Lock解決了幻讀的問題。Next-Key Lock是行鎖和間隙鎖的組合,當InnoDB掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之後,其他事務就不能在這個間隙修改或者插入記錄。

Serializable 是最高的隔離級别,它通過強制事務串行執行,避免了幻讀的問題,但是 Serializable 會在讀取的每一行資料上都加鎖,是以可能導緻大量的逾時和鎖争用的問題,是以并發度急劇下降,在MySQL InnoDB同樣不被建議使用。

開啟事務

  • BEGIN、BEGIN WORK、START TRANSACTION

    執行BEGIN指令不會真正在引擎層開啟新事務,僅僅是為目前線程設定标記,表示為顯式開啟的事務。

  • START TRANSACTION READ ONLY

    開啟隻讀事務,當MySQL Server接收到任何資料更改的SQL時,都會直接拒絕修改并傳回錯誤,此錯我不會進入引擎層。

  • START TRANSACTION READ WRITE

    允許super使用者在目前線程隻讀狀态為true的情況下啟動讀寫事務。

  • START TRANSACTION WITH CONSISTENT SNAPSHOT

    開啟事務會進入引擎層,并開啟一個readview。隻有在RR隔離級别下,這種操作才有效,否則會報錯。

Undo log

在資料進行修改時會記錄相應的undo日志,如果事務失敗或者復原,可以借助記錄的undo日志進行復原。Undo log是邏輯日志,記錄更改前的資料鏡像。在修改時如果同時需要讀取目前資料的時候,它可以根據版本資訊分析出該行記錄以前版本的資料。另外Undo log也會産生重做日志,因為Undo log也要進行持久化保護。

事務送出

  1. 使用全局事務ID産生器生成事務NO,将目前連接配接的事務指針(trx_t)添加到全局送出事務連結清單(trx_serial_list)中
  2. 标記undo,如果這個事務隻使用了一個UndoPage且使用量小于3/4個Page,則把這個Page标記為TRX_UNDO_CACHED,如果不滿足且是insert undo則标記為TRX_UNDO_TO_FREE,否則undo為update undo則标記為TRX_UNDO_TO_PURGE。标記為TRX_UNDO_CACHED的undo會被引擎回收。
  3. 把update undo放入所在undo segment的history list,并遞增rseg_history_len(全局)。同時更新Page上的TRX_UNDO_TRX_NO, 如果删除了資料,則重置delete_mark
  4. 把undate undo從update_undo_list中删除,如果被标記為TRX_UNDO_CACHED,則加入到update_undo_cached隊列中
  5. mtr_commit(日志undo/redo寫入公共緩沖區),至此,在檔案層次事務送出。這個時候即使crash,重新開機後依然能保證事務是被送出的。接下來要做的是記憶體資料狀态的更新(trx_commit_in_memory)
  6. 隻讀事務隻需要把readview從全局readview連結清單中移除,然後重置trx_t結構體裡面的資訊即可。讀寫事務首先需要是設定事務狀态為TRX_STATE_COMMITTED_IN_MEMORY,釋放所有行鎖并且将trx_t從rw_trx_list中移除,readview從全局readview連結清單中移除。如果有insert undo則在這裡移除,如果有update undo則喚醒Purge線程進行垃圾清理,最後重置trx_t裡的資訊,便于下一個事務使用

復原

  • 如果是隻讀事務,則直接傳回
  • 判斷目前是復原整個事務還是部分事務,如果是部分事務,則記錄下需要保留多少個Undo log,多餘的全進行復原
  • 從update undo和insert undo中找出最後一條undo,從這條undo開始復原
  • 如果是update undo則将标記為删除的記錄清理标記,更新過的資料復原到最老的版本。如果是insert undo則直接删除聚集索引和二級索引
  • 如果所有undo都已經被復原或者復原到了指定的undo則停止,把Undo log删除

索引

InnoDB引擎使用B+樹作為索引結構,主鍵索引的葉子節點data域儲存了完整的字段資料,非主鍵索引的葉子節點儲存了指向主鍵的值資料。

MySQL InnoDB 存儲引擎探秘

上圖是 InnoDB 主索引(同時也是資料檔案)的示意圖,可以看到葉節點包含了完整的資料記錄,這種索引叫做聚集索引。因為 InnoDB 的資料檔案本身要按主鍵聚集,是以 InnoDB 要求表必須有主鍵,如果沒有顯式指定,則 MySQL 系統會自動選擇一個可以唯一辨別資料記錄的列作為主鍵,如果不存在這種列,則 MySQL 自動為 InnoDB 表生成一個隐含字段作為主鍵,這個字段長度為6個位元組,類型為長整形。

InnoDB 的輔助索引 data 域存儲相應記錄主鍵的值而不是位址。換句話說,InnoDB 的所有輔助索引都引用主鍵作為 data 域。聚集索引這種實作方式使得按主鍵的搜尋十分高效,但是輔助索引搜尋需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

結尾

對于MySQL InnoDB的諸多特性,本文隻介紹了很小的一部分,感興趣的同學可閱讀 《MySQL技術内幕:InnoDB存儲引擎》了解更多相關知識。

繼續閱讀