參閱:《innodb存儲引擎內幕》
原創文章,會不定時更新,轉發請標明出處:http://www.cnblogs.com/janehoo/p/7717041.htmlhtml
1、概述:mysql
innodb的整個體系架構就是由多個內存塊組成的緩衝池及多個後臺線程構成。緩衝池緩存磁盤數據(解決cpu速度和磁盤速度的嚴重不匹配問題),後臺進程保證緩存池和磁盤數據的一致性(讀取、刷新),並保證數據異常宕機時能恢復到正常狀態。linux
緩衝池主要分爲三個部分:redo log buffer、innodb_buffer_pool、innodb_additional_mem_pool。sql
後臺進程分爲:master thread,IO thread,purge thread,page cleaner thread。 數據庫
master thread根據服務器的壓力分爲了每一秒及每十秒的操做。每一秒的操做包括:刷新重作日誌、根據過去一秒的磁盤吞吐量來判斷是否須要merge insert buffer、根據髒頁在緩衝池中佔比是否超過最大髒頁佔比及是否開啓自適應刷新來刷新髒頁。每十秒的操做包括:根據過去10秒的磁盤吞吐量來刷新髒頁,刷新重作日誌,回收undo 頁,再根據髒頁佔比是否超過70%刷新定量髒頁。windows
innodb總體的體系結構以下圖所示:數組
2、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內部各部分的協調管理簡圖:
3、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格式,可是主從數據的一致性無法保證。