一、引言

在MySQL做完優化工作後,真正執行SQL語句的部件是存儲引擎。在MySQL眾多引擎之中,InnoDB是目前默認的存儲引擎,也是使用最廣泛的引擎。InnoDB是事務安全的MySQL存儲引擎,支持ACID事務。其設計目標主要面向在線事務處理(OLTP)的應用。其特點是行鎖設計、支持外鍵,並支持非鎖定讀,即默認讀操作不會產生鎖。InnoDB存儲數據是基於磁碟存儲的,且其記錄是按照頁的方式進行管理。那麼將引出如下疑問:

  1. 當前的磁碟速度和CPU之間擁有一條巨大的鴻溝,InnoDB如何解決?。
  2. 資料庫對數據進行操作(查詢,修改,插入)時,數據在磁碟上的位置是隨機的,將會更加影響操作數據的性能,InnoDB又是如何解決的?
  3. 針對問題1,非常有效的辦法是引入緩存來解決,但是引入緩存後,會導致緩存數據和磁碟數據一致性和MySQL非正常死亡時緩存數據丟失的問題。
  4. InnoDB有哪些重要特性?

二、InnoDB體系架構

如圖所示,InnoDB存儲引擎由內存池和一些後台線程組成,其各自主要的工作是:

內存池主要工作

  • 維護所有進程/線程需要訪問的多個內部數據結構
  • 緩存磁碟上的數據,方便快速讀取,同時在對磁碟文件修改之前進行緩存
  • 緩存重做日誌(redo log)

後台線程主要工作

  • 刷新內存池中的數據,保證緩衝池中緩存的數據最新
  • 將已修改數據文件刷新到磁碟文件
  • 保證資料庫異常時InnoDB能恢復到正常運行狀態

2.1、 InnoDB內存池

2.1.1. InnoDB內存池架構圖

2.1.2、 緩衝池InnoDB緩衝池是為了通過內存的速度來彌補磁碟速度慢對資料庫性能造成的影響。其工作方式總是將資料庫文件按頁(每頁16K)讀取到緩衝池,然後按最近最少使用(LRU)的演算法來保留在緩衝池中的緩存數據。在資料庫中進行讀操作時,首先將從磁碟讀到的頁存放在緩衝池中,下一次讀取相同的頁時,首先判定是否存在緩衝池中,如果有就是被命中直接讀取,沒有的話就從磁碟中讀取。在資料庫進行改操作時,首先修改緩衝池中的頁(修改後,該頁即為臟頁),然後在以一定的頻率刷新到磁碟上。這裡的刷新機制不是每頁在發生變更時觸發。而是通過一種checkpoint機制刷新到磁碟的。所以緩衝池的大小直接影響著資料庫的整體性能,可以通過配置參數innodb_buffer_pool_size來設置。從架構圖中可以看出緩衝池中緩存的數據頁類型有:索引頁、數據頁、 undo 頁、插入緩衝、自適應哈希索引、 InnoDB 的鎖信息、數據字典信息等。索引頁和數據頁占緩衝池的很大一部分。

  • 數據頁和索引頁: Page是Innodb存儲的最基本結構,也是Innodb磁碟管理的最小單位,與資料庫相關的所有內容都存儲在Page結構里。Page分為幾種類型,數據頁和索引頁就是其中最為重要的兩種類型。
  • 插入緩存: 在InnoDB引擎上進行插入操作時,一般需要按照主鍵順序進行插入,這樣才能獲得較高的插入性能。當一張表中存在非聚簇的且不唯一的索引時,在插入時,數據頁的存放還是按照主鍵進行順序存放,但是對於非聚簇索引葉節點的插入不再是順序的了,這時就需要離散的訪問非聚簇索引頁,由於隨機讀取的存在導致插入操作性能下降。

InnoDB為此設計了Insert Buffer來進行插入優化。對於非聚簇索引的插入或者更新操作,不是每一次都直接插入到索引頁中,而是先判斷插入的非聚集索引是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer中。看似資料庫這個非聚集的索引已經查到葉節點,而實際沒有,這時存放在另外一個位置。然後再以一定的頻率和情況進行Insert Buffer和非聚簇索引頁子節點的合併操作。這時通常能夠將多個插入合併到一個操作中,這樣就大大提高了對於非聚簇索引的插入性能。

  • 自適應哈希索引: InnoDB會根據訪問的頻率和模式,為熱點頁建立哈希索引,來提高查詢效率。InnoDB存儲引擎會監控對錶上各個索引頁的查詢,如果觀察到建立哈希索引可以帶來速度上的提升,則建立哈希索引,所以叫做自適應哈希索引。

自適應哈希索引是通過緩衝池的B+樹頁構建而來,因此建立速度很快,而且不需要對整張數據表建立哈希索引。其 有一個要求,即對這個頁的連續訪問模式必須是一樣的,也就是說其查詢的條件(WHERE)必須完全一樣,而且必須是連續的。

  • 鎖信息 : nnoDB存儲引擎會在行級別上對錶數據進行上鎖。不過InnoDB也會在資料庫內部其他很多地方使用鎖,從而允許對多種不同資源提供並發訪問。資料庫系統使用鎖是為了支持對共享資源進行並發訪問,提供數據的完整性和一致性。關於鎖的具體知識我們之後再進行詳細學習。
  • 數據字典信息 : InnoDB有自己的表緩存,可以稱為表定義緩存或者數據字典。當InnoDB打開一張表,就增加一個對應的對象到數據字典。

數據字典是對資料庫中的數據、庫對象、表對象等的元信息的集合。在MySQL中,數據字典信息內容就包括表結構、資料庫名或表名、欄位的數據類型、視圖、索引、表欄位信息、存儲過程、觸發器等內容。MySQL INFORMATION_SCHEMA庫提供了對數據局元數據、統計信息、以及有關MySQL server的訪問信息(例如:資料庫名或表名,欄位的數據類型和訪問許可權等)。該庫中保存的信息也可以稱為MySQL的數據字典。

2.1.3. 重做日誌沖池

InnoDB有buffer pool(簡稱bp)。bp是資料庫頁面的緩存,對InnoDB的任何修改操作都會首先在bp的page上進行,然後這樣的頁面將被標記為dirty並被放到專門的flush list上,後續將由master thread或專門的刷臟線程階段性的將這些頁面寫入磁碟(disk or ssd)。這樣的好處是避免每次寫操作都操作磁碟導致大量的隨機IO,階段性的刷臟可以將多次對頁面的修改merge成一次IO操作,同時非同步寫入也降低了訪問的時延。然而,如果在dirty page還未刷入磁碟時,server非正常關閉,這些修改操作將會丟失,如果寫入操作正在進行,甚至會由於損壞數據文件導致資料庫不可用。為了避免上述問題的發生,Innodb將所有對頁面的修改操作寫入一個專門的文件,並在資料庫啟動時從此文件進行恢復操作,這個文件就是redo log file。這樣的技術推遲了bp頁面的刷新,從而提升了資料庫的吞吐,有效的降低了訪問時延。帶來的問題是額外的寫redo log操作的開銷(順序IO,當然很快),以及資料庫啟動時恢復操作所需的時間。

redo日誌由兩部分構成:redo log buffer、redo log file。innodb是支持事務的存儲引擎,在事務提交時,必須先將該事務的所有日誌寫入到redo日誌文件中,待事務的commit操作完成才算整個事務操作完成。在每次將redo log buffer寫入redo log file後,都需要調用一次fsync操作,因為重做日誌緩衝只是把內容先寫入操作系統的緩衝系統中,並沒有確保直接寫入到磁碟上,所以必須進行一次fsync操作。因此,磁碟的性能在一定程度上也決定了事務提交的性能。

InnoDB 存儲引擎先將重做日誌信息放入這個緩衝區,然後以一定頻率將其刷新到重做日誌文件。重做日誌文件一般不需要設置得很大,因為在下列三種情況下重做日誌緩衝中的內容會刷新到磁碟的重做日誌文件中。

  • Master Thread 每一秒將重做日誌緩衝刷新到重做日誌文件
  • 每個事物提交時會將重做日誌緩衝刷新到重做日誌文件
  • 當重做日誌緩衝剩餘空間小於1/2時,重做日誌緩衝刷新到重做日誌文件

2.1.4. 額外的緩衝池在 InnoDB 存儲引擎中,對一些數據結構本身的內存進行分配時,需要從額外的內存池中進行申請。例如: 分配了緩衝池,但是每個緩衝池中的幀緩衝還有對應的緩衝控制對象,這些對象記錄以一些諸如 LRU, 鎖,等待等信息,而這個對象的內存需要從額外的內存池中申請。

2.2 主要後台線程

2.2.1. master thread 核心的後台線程,主要負責將緩衝池中的數據非同步刷新到磁碟,保證數據的一致性,包括臟頁的刷新、合併插入緩衝、undo頁的回收等。Master thread在主循環中,分兩大部分操作,每秒鐘的操作和每10秒鐘的操作:

  • 每秒一次的操作
    • 日誌緩衝刷新到磁碟: 即使這個事務還沒有提交(總是),這點解釋了為什麼再大的事務commit時都很快;
    • 合併插入緩衝(可能): 合併插入並不是每秒都發生,InnoDB會判斷當前一秒內發生的IO次數是否小於5,如果是,則系統認為當前的IO壓力很小,可以執行合併插入緩衝的操作。
    • 至多刷新100個InnoDB的緩衝池的臟頁到磁碟(可能) : 這個刷新100個臟頁也不是每秒都在做,InnoDB引擎通過判斷當前緩衝池中臟頁的比例(buf_get_modified_ratio_pct)是否超過了配置文件中innodb_max_drity_pages_pct參數(默認是90,即90%),如果超過了這個閾值,InnoDB引擎認為需要做磁碟同步操作,將100個臟頁寫入磁碟。

  • 每10秒一次的操作
    • 刷新100個臟頁到磁碟(可能): InnoDB引擎先判斷過去10秒內磁碟的IO操作是否小於200次,如果是,認為當前磁碟有足夠的IO操作能力,即將100個臟頁刷新到磁碟。
    • 合併至多5個插入緩衝(總是): 此次的合併插入緩衝操作總會執行,不同於每秒操作時可能發生的合併操作。
    • 將日誌緩衝刷新到磁碟(總是): InnoDB引擎會再次執行日誌緩衝刷新到磁碟的操作,與每秒發生的操作一樣。
    • 刪除無用的undo頁(總是): 當對錶執行update,delete操作時,原先的行會被標記為刪除,但是為了一致性讀的關係,需保留這些行版本的信息,在進行10S一次的刪除操作時,InnoDB引擎會判斷當前事務系統中已被刪除的行是否可以刪除,如果可以,InnoDB會立即將其刪除。InnoDB每次最多刪除20個Undo頁。
    • 產生一個檢查點(checkpoing);

2.2.2. IO threads在 InnoDB 存儲引擎中大量使用了非同步 IO 來處理寫 IO 請求,IO Thread 的工作主要是負責這些 IO 請求的回調.。分別為write、read、insert buffer和log IO thread。線程數量可以通過參數進行調整。5.6以後的版本可以通過innodb_write_io_threads和innodb_read_io_threads來限制讀寫線程,而在5.6版本以前,只有一個參數innodb_file_io_threads來控制讀寫匯流排程數。

2.2.3. purge threads負責回收已經使用並分配的undo頁,purge操作默認是由master thread中完成的,為了減輕master thread的工作,提高cpu使用率以及提升存儲引擎的性能。用戶可以在參數文件中添加如下命令來啟動獨立的purge thread。innodb_purge_threads=1從innodb1.2版本開始,可以指定多個innodb_purge_threads來進一步加快和提高undo回收速度。

2.2.4. page cleaner threadsPage Cleaner Thread是在InnoDB1.2.X版本中引入的。其作用是將之前版本中臟頁的刷新操作都放入到單獨的線程中來完成。 其目的是減輕master thread的工作以及對於用戶查詢線程的阻塞,進一步提高InnoDB存儲引擎的性能。

三、InnoDB重要特性

MySQL InnoDB通過如下重要特性實現了更好的新能和更高的特性

  • 插入緩衝(insert buffer)
  • 兩次寫(Double write)
  • 自適應哈希索引(adaptive hash index)
  • 非同步io(Async IO)
  • 刷新領接頁(Flush Neighbor Page)

3.1 插入緩衝

3.1.1. 舉個栗子我們去圖書館還書,對應圖書館來說,他是做了insert(增加)操作,管理員在1小時內接受了100本書,這時候他有2種做法把還回來的書歸位到書架上:1)每還回來一本書,根據這本書的編碼(書櫃區-排-號)把書送回架上2)暫時不做歸位操作,先放到櫃面上,等不忙的時候,再把這些書按照書櫃區-排-號先排好,然後一次性歸位用方法1,管理員需要進出(IO)藏書區100次,不停的登高爬低完成圖書歸位操作,累死累活,效率很差。用方法2,管理員只需要進出(IO)藏書區1次,對同一個位置的書,不管多少,都只要爬一次樓梯,大大減輕了管理員的工作量。所以圖書館都是按照方法2來做還書動作的。但是你要說,我的圖書館就20本書,1個0.5米的架子,方法2和1管理起來都很方便,這種情況不在我們討論的範圍。當數據量非常小的時候,就不存在效率問題了。

關係資料庫在處理插入操作的時候,處理的方法和上面類似,每一次插入都相當於還一本書,它也需要一個櫃檯來保存插入的數據,然後分類歸檔,在不忙的時候做批量的歸位。這個櫃檯就是insert buffer.這就是為什麼會有insert buffer,更多的是處於性能優化的考慮。

3.1.2. 什麼是插入緩衝insert buffer是一種特殊的數據結構(B+ tree)並不是緩存的一部分,而是物理頁。對於非聚集索引的插入或更新操作,不是每一次直接插入索引頁.而是先判斷插入的非聚集索引頁是否在緩衝池中.如果在,則直接插入,如果不再,則先放入一個插入緩衝區中.然後再以一定的頻率執行插入緩衝和非聚集索引頁子節點的合併操作.使用條件:非聚集索引,非唯一,原因如下:

  • primary key 是按照遞增的順序進行插入的,異常插入聚族索引一般也順序的,非隨機IO。
  • 寫唯一索引要檢查記錄是不是存在,所以在修改唯一索引之前,必須把修改的記錄相關的索引頁讀出來才知道是不是唯一、這樣Insert buffer就沒意義了,要讀出來(隨機IO),所以只對非唯一索引有效。

3.1.3. insert buffer的原理對於為非唯一索引,輔助索引的修改操作並非實時更新索引的葉子頁,而是把若干對同一頁面的更新緩存起來做,合併為一次性更新操 作,減少IO,轉隨機IO為順序IO,這樣可以避免隨機IO帶來性能損耗,提高資料庫的寫性能,具體流程:1) 先判斷要更新的這一頁在不在緩衝池中a、若在,則直接插入;b、若不在,則將index page 存入Insert Buffer,按照Master Thread的調度規則來合併非唯一索引和索引頁中的葉子結點

2) Master Thread的調度規則a、主動merger: innodb主線程定期完成,用戶線程無感知主動merge通過innodb主線程(svr_master_thread)判斷:若過去1s之內發生的I/O小於系統I/O能力的5%,則主動進行一次insert buffer的merge操作。merge的頁面數為系統I/O能力的5%,讀取採用async io模式。每10s,必定觸發一次insert buffer meger操作。meger的頁面數仍舊為系統 I/O能力的5%。

  • 主線程發出async io請求,async讀取需要被merge的索引頁面
  • I/O handler 線程,在接受到完成的async I/O之後,進行merge

b 、被動merge: 用戶線程完成,用戶能感受到meger操作帶來的性能影響

  • insert操作,導致頁面空間不足,需要分裂(split)。由於insert buffer只針對單個頁面,不能buffer page split[頁已經在內存里],因此引起頁面的被動meger。同理,update操作導致頁面空間不 足;purge導致頁面為空等。總之,若當前操作引起頁面split or merge,那麼就會導致被動merge;
  • insert操作,由於其它各種原因,insert buffer優化返回false,需要真正讀取page時,要進行被動merge。與一不同的是,頁在disk上,需要讀取到內存里;
  • 在進行insert buffer操作,發現insert buffer太大,需要壓縮insert buffer,這時需要強制被動merge,不允許 insert 操作進行。

3.2 兩次寫

Insert Buffer帶給InnoDB存儲引擎的是性能上的提升,doublewrite(兩次寫)帶給InnoDB存儲引擎的是數據頁的可靠性。當發生資料庫宕機時,可能InnoDB存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,比如16KB的頁,只寫了前4KB,之後就發生了宕機,這種情況被稱為部分寫失效(partial page write)。在InnoDB存儲引擎未使用doublewrite技術前,曾經出現過因為部分寫失效而導致數據丟失的情況。有經驗的DBA也許會想,如果發生寫失效,可以通過重做日誌進行恢復。這是一個辦法。但是必須清楚地認識到,重做日誌中記錄的是對頁的物理操作,如偏移量800,寫『aaaa』記錄。如果這個頁本身已經發生了損壞,再對其進行重做是沒有意義的。這就是說,在應用重做日誌前,用戶需要一個頁的副本,當寫入失效發生時,先通過頁的副本來還原該頁,再進行重做,這就是doublewrite。在InnoDB存儲引擎中doublewrite的體系架構如圖所示:

3.3 自適應哈希索引

哈希(hash)是一種非常快的查找方法,在一般情況下這種查找的時間複雜度為O(1),即一般僅需要一次查找就能定位數據。 而B+樹的查找次數,取決於B+樹的高度,在生產環境中,B+樹的高度一般為3~4層,所以需要3~4次的查詢。

InnoDB存儲引擎會監控對錶上各索引頁的查詢。如果觀察到建立哈希索引可以帶來速度提升,則建立哈希索引,稱之為自適應哈希索引(Adaptive Hash Index, AHI)。AHI是通過緩衝池的B+樹頁構造而來,因此建立的速度很快,而且不需要對整張表構建哈希索引。InnoDB存儲引擎會自動根據訪問的頻率和模式來自動地為某些熱點頁建立哈希索引。

AHI有一個要求,對這個頁的連續訪問模式必須是一樣的。例如對於(a,b)這樣的聯合索引頁,其訪問模式可以是下面情況:

  • where a=xxx
  • where a =xxx and b=xxx

訪問模式一樣是指查詢的條件是一樣的,若交替進行上述兩種查詢,那麼InnoDB存儲引擎不會對該頁構造AHI。 AHI還有下面幾個要求:

  • 以該模式訪問了100次
  • 頁通過該模式訪問了N次,其中N=頁中記錄*1/16

InnoDB存儲引擎官方文檔顯示,啟用AHI後,讀取和寫入速度可以提高2倍,輔助索引的連接操作性能可以提高5倍。AHI的設計思想是資料庫自優化,不需要DBA對資料庫進行手動調整。

3.4 非同步IO

  • sync IO :同步IO 即每進行一次IO操作,此次操作結束才能繼續接下來的操作。 但是如果用戶發需要等待出一條索引掃描的查詢,那麼這條SQL查詢語句可能需要掃描多個索引頁,也就是需要進行多次的IO操作。在每掃描一個頁並等待期完成再進行下一次的掃描是沒有必要的。
  • 非同步IO: 用戶可以在發出一個IO請求後立即再發出另一個IO請求,當全部IO請求發送完畢後,等待所有IO操作的完成,這就是AIO。

AIO另一個優勢可以將多個IO,合併為1個IO,以提高IO效率。例如:用戶需要訪問3頁內容,但這3頁時連續的。同步IO需要進行3次IO,而AIO只需要一次 就可以了。

3.5 刷新領接頁

當刷新一個臟頁時,innodb會檢測該頁所在區(extent)的所有頁,如果是臟頁,那麼一起進行刷新。這樣做,通過AIO將多個IO寫入操作合併為一個IO操作。在傳統機械磁碟下有著顯著優勢。

參考文獻

MySQL探秘(三):InnoDB的內存結構和特性innodb存儲引擎


推薦閱讀:
相关文章