1. 程式人生 > >[小結]InnoDB體系結構及工作原理

[小結]InnoDB體系結構及工作原理

參閱:《innodb儲存引擎內幕》
原創文章,會不定時更新,轉發請標明出處:http://www.cnblogs.com/janehoo/p/7717041.html
一、概述:
  innodb的整個體系架構就是由多個記憶體塊組成的緩衝池及多個後臺執行緒構成。緩衝池快取磁碟資料(解決cpu速度和磁碟速度的嚴重不匹配問題),後臺程序保證快取池和磁碟資料的一致性(讀取、重新整理),並保證資料異常宕機時能恢復到正常狀態。
  緩衝池主要分為三個部分:redo log buffer、innodb_buffer_pool、innodb_additional_mem_pool。
* innodb_buffer_pool由包含資料、索引、insert buffer ,adaptive hash index,lock 資訊及資料字典。
* redo log buffer用來快取重做日誌。
* additional memory pool:用來快取LRU連結串列、等待、鎖等資料結構。

後臺程序分為:master thread,IO thread,purge thread,page cleaner thread。
* master thread負責重新整理快取資料到磁碟並協調排程其它後臺程序。
* IO thread 分為 insert buffer、log、read、write程序。分別用來處理insert buffer、重做日誌、讀寫請求的IO回撥。
*purge thread用來回收undo 頁
* page cleaner thread用來重新整理髒頁。

master thread根據伺服器的壓力分為了每一秒及每十秒的操作。每一秒的操作包括:重新整理重做日誌、根據過去一秒的磁碟吞吐量來判斷是否需要merge insert buffer、根據髒頁在緩衝池中佔比是否超過最大髒頁佔比及是否開啟自適應重新整理來重新整理髒頁。每十秒的操作包括:根據過去10秒的磁碟吞吐量來重新整理髒頁,重新整理重做日誌,回收undo 頁,再根據髒頁佔比是否超過70%重新整理定量髒頁。
innodb整體的體系結構如下圖所示:
這裡寫圖片描述

二、innodb內部協調管理
  一條SQL進入MySQL伺服器,會依次經過連線池模組(進行鑑權,生成執行緒),查詢快取模組(是否被快取過),SQL介面模組(簡單的語法校驗),查詢解析模組,優化器模組(生成語法樹),然後再進入innodb儲存引擎。進入innodb後,首先會判斷該SQL涉及到的頁是否存在於快取中,如果不存在則從磁碟讀取相應索引及資料頁載入至快取。如果是select語句,讀取資料(使用一致性非鎖定讀),並將查詢結果返回至伺服器層。如果是DML語句,讀取到相關頁,先試圖給這個SQL涉及到的記錄加鎖。加鎖成功後,先寫undo 頁,邏輯地記錄這些記錄修改前的狀態。然後再修改相關記錄,這些操作會同步物理地記錄至redo log buffer。如果涉及及非唯一輔助索引的更新,還需要使用insert buffer。事務提交時,會啟用內部分散式事務,先將SQL語句記錄到binlog中,再根據系統設定重新整理redo log buffer至redo log,保證binlog與redo log的一致性。提交後,事務會釋放對這些記錄所加的鎖,並將這些修改的記錄所在的頁放入innodb的flush list中,等待被page cleaner thread重新整理到磁碟。這個事務產生的undo page如果沒有被其它事務引用(insert的undo page不會被其它事務引用),就會被放入history list中,等待被purge執行緒回收。
  需要注意的是:
  a.髒頁的重新整理採用的是checkpoint機制
  b.DML語句不同undo頁的格式也會不同。insert型別的undo log只記錄了主鍵及對應的主鍵值,而update、delete則記錄了主鍵及所有變更的欄位值
  c.一條設計不好的SQL,可能會導致大量的離散讀、載入很多冗餘的資料頁至快取中
以下為innodb內部各部分的協調管理簡圖:
這裡寫圖片描述

##三、innodb內部關鍵技術
checkpoint:
如果我們有足夠大的記憶體且可以接受漫長的資料庫恢復時間的話,那我們沒有必要引入checkpoint機制。checkpoint通過標誌redo log不可用,重新整理快取中的髒頁,解決記憶體容量瓶頸,縮短恢復時間。innodb會在四種情況下會觸發checkpoint:master thead的定時重新整理、LRU列表中沒有足夠的空閒頁時(髒頁太多時)、redo log不可用時(async/sync flush checkpoint)及資料庫關閉時。checkpoint有兩種工作模式sharp checkpoint和 fuzzy checkpoint。一般情況下都是使用fuzzy checkpoint(重新整理部分髒頁),只有資料庫關閉且設定了innodb_fast_shutdown=1時,才會使用sharp checkpoint(重新整理所有髒頁回磁碟)。innodb系統日誌會根據redo log的生命週期儲存四個LSN號。分別是:當前系統LSN最大值、當前已經寫入日誌檔案的最大LSN號、已經重新整理到磁碟的資料頁的最大LSN、已經寫入檢查點的LSN,後面的LSN值總是小於等於前面的LSN值。當資料庫宕機時,可以通過只恢復檢查點的LSN至已經寫入到日誌檔案的最大LSN之間的資料來恢復資料庫。需要注意的是當髒頁容量觸碰到低水位線時,呼叫async flush checkpoint非同步重新整理髒頁至磁碟,當髒頁容量觸碰到高水位線時會呼叫sync flush checkpoint 瘋狂重新整理髒頁,磁碟會很忙,存在IO風暴。低水位線=75%total_redo_log_file_size 高水位線=90%total_redo_log_file_size
insert buffer:
專門為維護非唯一輔助索引的更新設計的。因為innodb的記錄是按主鍵的順序存放的,所以主鍵的插入是順序的,而聚集索引對應的輔助索引的更新則是離散的,為了避免大量離散讀寫,先檢查要更新的索引頁是否已經快取在了記憶體中,如果沒有,先將輔助索引的更新都放入緩衝(inset buffer區),等待合適機會(master thread的定時操作,索引塊需要被讀取時,insert buffer bitmap檢測到對應的索引頁不夠用時)進行insert buffer和索引頁的合併。因為輔助索引快取到insert buffer中時並不會讀取磁碟上的索引頁,以至於無法校驗索引的唯一性,所以不適用唯一輔助索引。innodb中所有的非唯一輔助索引的insert buffer均由同一棵二叉樹維護。二叉樹的非葉子節點由space(表空間id)+marker(相容老版本的insert buffer)+offset(在表空間中的位置)構成,葉子節點由space+marker+offset+metadata(進入順序+型別+標誌)+輔助索引構成,進行merge合併時,按順序進行回放。mysql5.1之後,insert buffer支援change_buffer,還可以緩衝非唯一輔助索引的update\delete操作。insert buffer的二叉樹結構是存放在共享表空間中的,所以通過獨立表空間恢復表時,執行check table操作會失敗,因為輔助索引的資料可能還在insert buffer中,需要通過repair table 重建表上全部的輔助索引。為了保證每次 merge insert buffer成功,表空間中每隔256個連續區就有一個insert buffer bitmap頁用來記錄索引頁的可用空間。insert buffer bitmap頁總是處於這個連續區間的第二頁,每個索引頁在insert buffer bitmap中佔4 bit。可以通過show engine innodb stauts\G;檢視insert buffer and adaptive hash index 檢視insert buffer的合併數量、空閒頁數量、本身的大小、合併次數及索引操作次數。通過索引操作次數與合併次數的的比例可以判斷出insert buffer所帶來的效能提升。
double write:
因為髒頁重新整理到磁碟的寫入單元小於單個頁的大小,如果在寫入過程中資料庫突然宕機,可能會使資料頁的寫入不完成,造成資料頁的損壞。而redo log中記錄的是對頁的物理操作,如果資料頁損壞了,通過redo log也無法進行恢復。所以為了保證資料頁的寫入安全,引入了double write。double write的實現分兩個部分,一個是緩衝池中2M的記憶體塊大小,一個是共享表空間中連續的128個頁,大小是2M。髒頁從flush list重新整理時,並不是直接重新整理到磁碟而是先呼叫函式(memcpy),將髒頁拷貝到double write buffer中,然後再分兩次,每次1M將double write buffer 重新整理到磁碟double write 區,之後再呼叫fsync操作,同步到磁碟。如果應用在業務高峰期,innodb_dblwr_pages_written:innodb_dblwr_writes遠小於64:1,則說明,系統寫入壓力不大。雖然,double write buffer重新整理到磁碟的時候是順序寫,但還是是有效能損耗的。如果系統本身支援頁的安全性保障(部分寫失效防範機制),如ZFS,那麼就可以禁用掉該特性(skip_innodb_doublewrite)。
adaptive hash index:
innodb會對錶上的索引頁的查詢進行監控,如果發現建立hash索引能夠帶來效能提升,就自動建立hash索引。hash索引的建立是有條件的,首先是必定能夠帶來效能提升。其次資料庫以特定模式的連續訪問超過了100次,通過該模式被訪問的頁的訪問次數超過了1/16的記錄行數。自適應hash根據B+樹中的索引構造而來,只需為這個表的熱點頁構造hash索引而不是為整張表都構建。同樣可以通過show engine innodb status\G中的 insert buffer and adaptive hash index(hash searches/s non-hash searches)檢視hash index的使用情況。
重新整理鄰近頁:
innodb進行髒頁重新整理時,會檢查該髒頁所在區內是否還存在其它髒頁,如果存在則一同重新整理,通過AIO,進行IO合併,一定程度上減少了IO壓力。但是它也存在一個問題,就是把原本不怎麼髒的頁也重新整理到了磁碟。可能很快這個不怎麼髒的頁又被讀取到緩衝中,又增加了IO的壓力。對於普通的機械盤開啟這個特性可以帶來很大的效能提升,但是如果是讀寫速度非常高的隨機盤,可以關閉這個特性(innodb_flush_neighbors=0)效能反而會更好。(因為對該特性的維護也是需要消耗效能的)
非同步IO:
mysql 5.5之前並不支援非同步IO,而是通過innodb程式碼模擬實現。5.5之後開始提供AIO支援。資料庫可以連續發出IO請求,然後再等待IO請求的處理結果。非同步IO帶來的好處就是可以進行IO合併操作,減少磁碟壓力。要想mysql支援非同步IO還需要作業系統支援,首先作業系統必須支援非同步IO,像windows,linux都是支援的,但是 mac osx卻不支援。同時在編譯和執行時還需要有libaio依賴包。可以通過設定innodb_use_native_aio來控制是否啟用這個特性,一般開啟這個特性可以使資料恢復帶來75%的效能提升。
事務:
innodb中一個邏輯事務包含一組物理事務。不管是物理事務還是邏輯事務,都需要滿足ACID特性(原子性,一致性,隔離性,及永續性)。如果一個邏輯事務需要操作多個頁,那麼它對每個頁的操作會以一個物理事務來進行。物理事務對頁進行處理時,先根據頁的space_id,page_no找到對應的頁,再試圖對該頁加鎖。如果申請加的鎖和該頁原本已經加上的鎖衝突,則進入等待狀態。否則直接加鎖,並將該頁加入到memo動態陣列中,之後物理事務就可以訪問這個頁了。如果對該頁進行的是變更操作,那麼針對這些操作就會在local buffer中產生redo log record記錄。當物理事務提交時,會在redo log record後追加一串結束標誌日誌來保證物理事務的完整性。物理事務提交後,redo log record會被提交到redo log buffer的塊中,一個塊的大小是512位元組,一個redo log record可能會出現在多個塊中,這取決於redo log record的長度(每個塊開始的兩個位元組記錄的是第一個mtr在該段中開始的位置,如果是0,則表明還是上一個block的同一個mtr)。同時被分配到一個LSN號,LSN號確定了它在redo log中的位置,這個LSN號也將會被寫入到物理事務操作的頁的頁頭中。物理事務提交後,會檢查memo陣列中的這些頁是否被修改,若修改了則將其加入到innodb的flush list中。flush list中只能存放一個關於這個頁的記錄。如果頁沒有被修改,則直接釋放加在它上面的鎖。當邏輯事務提交時,會將redo log buffer以塊為單位順序重新整理到redo log中。多個邏輯事務併發時,可能會出現多個邏輯事務的物理事務交叉記錄在redo log buffer中。也會出現未提交的邏輯事務的部分物理事務日誌持久化在redo log中。但這並不會造成日誌重做的時候,重做未提交的邏輯事務。原因是,雖然重做的時候是以物理事務為單位進行重做,但它會判斷該物理事務所在的邏輯事務包含的所有物理事務是否完整,如果不完整,那麼該邏輯事務所涉及的所有物理事務都不會重做。物理事務的工作過程,可以很好的解釋一個邏輯事務在執行的過程中是在不斷地寫redo日誌,而且不斷地往flush list中加塞髒頁的。
innodb還支援內外部分散式事務。分散式事務的實現是:應用通過一個事務管理器實現對多個相同或不同的資料庫例項的事務管理。分散式事務與本地事務的區別是多了一個prepare的階段,待收到所有節點的同意資訊後再commit或rollback。內部分散式事務最常見的是binlog和innodb儲存引擎之間。事務提交時會先寫binlog再寫redo log,因為有內部分散式事務,在寫完binlog宕機的情況下,mysql再重啟會先檢查準備的uxid事務是否已經提交,若沒有則儲存引擎層再做一次提交。
MVCC:
多版本併發控制,mysql僅在RC,RR隔離級別下支援MVCC。主要是結合undo log來實現的一個數據的多個版本,保證讀不會堵塞寫,寫也不會堵塞讀來提高併發。mvcc下,select操作預設是一致性非鎖定讀,除非顯式給select加in share或for update鎖,才會使用一致性鎖定讀。
多隔離級別:
innodb支援四種隔離級別RU\RC\RR\serializable。RU不使用MVCC,讀取的時候也不加鎖。RC利用MVCC都是讀取記錄最新的版本,RR利用MVCC總是讀取記錄最舊的版本,並通過next-key locking來避免幻讀,serializable不使用MVCC,讀取記錄的時候加共享鎖,堵塞了其它事務對該記錄的更新,實現可序列化。隔離級別越高,維護成本越高,併發越低。RC隔離級別下要求二進位制日誌格式必須是row格式的,因為RC隔離級別下,不會加gap鎖,不能禁止一個事務在執行的過程中另一個事務對它的間隙進行操作的情況。這種情況下,對於事務開始的和提交的順序是先更改後提交,後更改先提交的情況,statement格式的binlog只會是按照事務提交的順序進行記錄。這可能會導致複製環境的slave資料和master資料不一致。通過設定innodb_locks_unsafe_for_binlog=1也可以使用statement格式,但是主從資料的一致性沒法保證。