1. 程式人生 > >探祕MySQL InnoDB 儲存引擎

探祕MySQL InnoDB 儲存引擎

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

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

體系架構

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 儲存引擎記憶體的結構

緩衝池

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: 1342177281 row in set (0.01 sec)

緩衝池中快取的資料頁型別有: 索引頁、undo頁、插入緩衝、自適應雜湊索引、InnoDB鎖資訊、資料字典資訊等,索引頁和資料頁佔緩衝池很大的一部分。

重做日誌緩衝

緩衝池中的頁資料比磁碟要新時,需要將新資料重新整理到磁碟中。InnoDB採用Write Ahead Log策略來重新整理資料,即當事務提交時,先寫入重做日誌緩衝,重做日誌緩衝會按一定頻率重新整理到重置日誌檔案中,然後髒頁會根據checkpoint機制重新整理到磁碟。

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

Master Thread每一秒將重做日誌緩衝重新整理到重做日誌檔案

每次事務提交時將重做日誌緩衝重新整理到重做日誌檔案

當重做日誌緩衝池剩餘空間小於1/2時,重做日誌緩衝重新整理到重做日誌檔案

額外記憶體池

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

InnoDB支援的鎖有:

1、共享鎖和排它鎖

2、意向鎖

3、記錄鎖

4、間隙鎖

5、自增鎖

共享鎖和排他鎖

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

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

意向鎖

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

意向共享鎖:將要去獲取某一行的共享鎖

意向排它鎖:將要去獲取某一行的排它鎖

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

記錄鎖

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

間隙鎖

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

自增鎖

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

想要學習Java架構技術的朋友可以加我的群:705127209,群內每晚都會有阿里技術大牛講解的最新Java架構技術。並會錄製錄播視訊分享在群公告中,作為給廣大朋友的加群的福利——分散式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高併發、高可用架構)/微服務(Spring Boot、Spring Cloud)/原始碼(Spring、Mybatis)/效能優化(JVM、TomCat、MySQL)

事務

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種隔離級別,分別是:

1、Read Uncommitted 未提交讀

2、Read Committed 已提交讀

3、Repeatable Read 可重複讀

4、Serializable 可序列化

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

髒讀

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

不可重複讀

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

幻讀

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

Read uncommitted 未提交讀

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

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

Read Committed 已提交讀

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

Repeatable Read 可重複讀

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

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

Serializable 可序列化

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也要進行持久化保護。

事務提交

使用全域性事務ID產生器生成事務NO,將當前連線的事務指標(trx_t)新增到全域性提交事務連結串列(trx_serial_list)中

標記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會被引擎回收。

把update undo放入所在undo segment的history list,並遞增rseg_history_len(全域性)。同時更新Page上的TRX_UNDO_TRX_NO, 如果刪除了資料,則重置delete_mark

把undate undo從update_undo_list中刪除,如果被標記為TRX_UNDO_CACHED,則加入到update_undo_cached佇列中

mtr_commit(日誌undo/redo寫入公共緩衝區),至此,在檔案層次事務提交。這個時候即使crash,重啟後依然能保證事務是被提交的。接下來要做的是記憶體資料狀態的更新(trx_commit_in_memory)

只讀事務只需要把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域儲存了完整的欄位資料,非主鍵索引的葉子節點儲存了指向主鍵的值資料。

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

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