InnoDB引擎架構介紹html
innodb存儲引擎的體系架構,可簡單劃分紅三層:前端
磁盤中數據文件如何存放,存放規則等,咱們稍後講解。本章主要說明內存池的模型和後臺線程功能,下圖展現了InnoDB的體系架構mysql
1、後臺進程算法
InnoDB存儲引擎是多線程的模型,所以其後臺有多個不一樣的線程,負責處理不一樣的任務sql
一、Master Thread數據庫
一個核心的後臺線程,主要負責將緩衝池中的數據異步刷新到磁盤,保證數據的一致性,包括髒頁的刷新、合併插入緩衝、UNDO頁的回收等緩存
二、IO Thread服務器
InnoDB存儲引擎大量使用了AIO(Async IO)來處理寫IO請求,這樣極大地提升了數據庫的性能。IO Thread的工做主要是負責這些IO請求的回調處理。共有四種IO Thread,分別是write, read,insert buff,log IO thread。默認的read thread和write thread是四個,能夠經過innodb_read_io_threads和innodb_write_io_threads參數來調整線程的數量。經過show engine innodb status\G查看IO thread。數據結構
FILE I/O -------- I/O thread 0 state: waiting for i/o request (insert buffer thread) I/O thread 1 state: waiting for i/o request (log thread) I/O thread 2 state: waiting for i/o request (read thread) I/O thread 3 state: waiting for i/o request (read thread) I/O thread 4 state: waiting for i/o request (read thread) I/O thread 5 state: waiting for i/o request (read thread) I/O thread 6 state: waiting for i/o request (write thread) I/O thread 7 state: waiting for i/o request (write thread) I/O thread 8 state: waiting for i/o request (write thread) I/O thread 9 state: waiting for i/o request (write thread) Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] , ibuf aio reads:, log i/o's:, sync i/o's: Pending flushes (fsync) log: 0; buffer pool: 0 394 OS file reads, 53 OS file writes, 7 OS fsyncs 11.00 reads/s, 16384 avg bytes/read, 5.00 writes/s, 0.62 fsyncs/s
三、Purge Thread事務完成後,其所使用的undolog可能不在須要,所以Purage Thread來回收已經使用並分配的undo頁。InnoDB支持多個purge thread,目的爲了進一步加快undo頁的回收。同時purge thread離散的讀取undo頁,這樣能更進一步利用磁盤的隨機讀取性能。多線程
undologUndo Log的原理很簡單,爲了知足事務的原子性,在操做任何數據以前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲UndoLog)。而後進行數據的修改。若是出現了錯誤或者用戶執行了ROLLBACK語句,系統能夠利用UndoLog中的備份將數據恢復到事務開始以前的狀態。除了能夠保證事務的原子性,Undo Log也能夠用來輔助完成事務的持久化。
經過innodb_purge_threads參數能夠修改Purge Thread的數量
mysql> show variables like '%purge%thread%'; +----------------------+-------+ | Variable_name | Value | +----------------------+-------+ | innodb_purge_threads | 4 | +----------------------+-------+
四、Page Cleaner Thread
Page Cleaner Thread做用是將以前版本中髒頁的刷新操做都放入到單獨的線程中來完成。目的是爲了減輕原Master Thread的工做及對於用戶查詢線程的阻塞,進一步提升InnoDB存儲引擎的性能。
mysql> show variables like 'innodb_page_cleaners'; +----------------------+-------+ | Variable_name | Value | +----------------------+-------+ | innodb_page_cleaners | 1 | +----------------------+-------+
2、內存
一、緩衝池
mysql> show variables like 'innodb_buffer_pool_size'; +-------------------------+-----------+ | Variable_name | Value | +-------------------------+-----------+ | innodb_buffer_pool_size | 134217728 | +-------------------------+-----------+
mysql> show variables like 'innodb_buffer_pool_instances'; +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | innodb_buffer_pool_instances | 8 | +------------------------------+-------+
informixmation_schema庫的INNODB_BUFFER_POOL_STATS表存放了緩衝池的狀態。
mysql> show variables like 'innodb_old_blocks_pct'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_old_blocks_pct | 37 | +-----------------------+-------+
37表示新讀取的頁差在LRU列表尾端的37%位置(差很少3/8)。在InnoDB引擎,midpoint以後的列表稱old列表,以前的稱new列表。能夠理解爲new列表中的頁都是最爲活躍的數據。
若是採用普通的LRU算法,直接將讀取到的頁放在LRU首部。那麼某些SQL操做可能會使緩衝池中的頁被刷出,影響緩衝池的效率。例如索引或數據的掃描操做,須要訪問許多頁甚至所有頁,這些頁僅在當前查詢中須要,並非熱點活躍數據。若是放在LRU首部,可能將須要的熱點數據從LRU隊列中移除,下一次須要讀取該頁時,又要從磁盤讀取。
爲了解決這個問題,引入了innodb_old_blocks_time參數,指定插入到舊子列表中的塊在第一次訪問後必須在那裏停留多長時間(以毫秒爲單位),而後才能移動到新子列表。若是值爲0,則插入到舊子列表中的塊在第一次訪問新子列表時當即移動到該子列表,不管插入後多久,訪問發生。若是該值大於0,則塊將保留在舊子列表中,直到訪問發生在在第一次訪問以後至少要通過幾毫秒。例如,值爲1000會致使塊停留在舊子列表在第一次訪問後1秒鐘,而後纔有資格移動到新子列表。這樣儘量使LRU列表中的數據不被刷出。
mysql> show variables like 'innodb_old_blocks_time'; +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | innodb_old_blocks_time | 1000 | +------------------------+-------+
LRU List來管理已經讀取的頁。當數據庫剛啓動,LRU列表是空的。這時頁都存放在Free列表中,當須要從緩衝池分頁時。首先從Free列表中查看是否有可用的空閒頁。如有將該頁從Free列表中刪除。放入到LRU列表。不然,根據LRU算法,淘汰LRU列表末端的頁。將該內存空間分配給新的頁。當頁從LRU的old部分到new部分時,此時的操做稱page made young。若是由於innodb_old_blocks_time致使頁沒有移動到new的操做稱爲page not made young。能夠經過show engine innodb status來觀察LRU列表和Free列表的使用狀況和運行狀態。
BUFFER POOL AND MEMORY ---------------------- Total large memory allocated 1099431936 Dictionary memory allocated 120760 Buffer pool size 65536 Free buffers 65137 Database pages 399 Old database pages 0 Modified db pages 0 Pending reads 0 Pending writes: LRU 0, flush list 0, single page 0 Pages made young 0, not young 0 0.00 youngs/s, 0.00 non-youngs/s Pages read 364, created 35, written 39 0.07 reads/s, 0.00 creates/s, 0.00 writes/s Buffer pool hit rate 958 / 1000, young-making rate 0 / 1000 not 0 / 1000 Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s LRU len: 399, unzip_LRU len: 0 I/O sum[0]:cur[0], unzip sum[0]:cur[0]
能夠看到,Buffer pool size 65536個頁。65536*16K = 1G。一個1G的緩衝池。Free buffers表示當前Free列表頁的數量,Database pages表示LRU列表的頁的數量。可能出現Free buffers與Database pages的數量之和不等於Buffer pool size。由於緩衝池的頁還會分配給自適應哈希索引,Lock信息,Insert Buffer等。這部分不須要LRU算法進行維護。
page made young表示LRU中頁移動到前端的次數。由於服務器運行階段沒有改變innodb_old_blocks_time的值,因此not young爲0,young/s non-youngs/s表示每秒這兩個操做的次數。
Buffer pool hit rate 表示緩衝池命中率,一般該值應該不小於95%。若是小於95,多是由於全表掃描引發LRU隊列污染。
注意:show engine innodb status顯示的不是數據庫的當前狀態。而是過去某個時間段的狀態,Per second averages calculated from the last 9 seconds表明過去9秒內數據庫的狀態。還能夠經過INNODB_BUFFER_POOL_STATS來檢測緩衝池的狀態。
經過INNODB_BUFFER_PAGE_LRU來觀察每一個LRU列表中每一個頁的具體信息
InnoDB引擎支持壓縮頁功能,將本來16K的頁壓縮爲1KB 2KB 4KB 8KB。因爲頁大小發生變化,LRU列表也發生變化。非16K的頁,從上邊代碼能夠看出 LRU len: 399, unzip_LRU len: 0
注意LRU的頁包含了unzip_LRU中的頁。對於壓縮頁的表,每一個表的壓縮比率不一樣,可能爲4k或8k。在unzip_LRU列表對不一樣壓縮頁大小分別管理。須要申請4KB頁的大小。步驟以下
(1)、檢查4kb的unzip_LRU列表,檢查是否有可用的空閒頁
(2)、如有,直接使用
(3)、不然,檢查8KB的unzip_LRU列表
(4)、若是獲得空閒頁,將頁分紅兩個4KB的頁,放入到4KB的unzip_LRU列表
(5)、若是不能獲得空閒頁,從LRU列表申請一個16KB的頁,分紅1個8KB和兩個4KB的頁,放到對於的列表中。
在LRU列表中的頁被修改後,該頁叫作髒頁。緩存池中的頁和磁盤中的頁不一致。數據庫經過檢查點機制將髒頁刷新到磁盤。Flush列表就是髒頁列表。須要注意的是,髒頁既存在於LRU列表,也存在於Flush列表。LRU列表管理緩衝池中頁的可用性,Flush列表管理將頁刷回磁盤。Flush列表也能夠經過show engine innodb status命令和INNODB_BUFFER_PAGE_LRU表查看。上圖中Modified db pages 指的是髒頁的數量。
三、重作日誌緩衝
InnoDB引擎的內存區除了緩衝池外,還有重作日誌緩衝(redo log buffer)。InnoDB首先將重作日誌信息放到這個緩衝區,而後按照必定頻率刷新到重作日誌文件。重作日誌緩衝不須要太大,通常狀況每秒會將重作日誌緩存刷新到新的日誌文件。用戶須要將每秒的的事務量控制在這個緩衝大小以內。該值由innodb_log_buffer_size控制,默認16M。重作日誌在如下三種狀況刷新到重作日誌文件。
(1)、Master Thread每秒將重作日誌緩衝刷新到重作日誌文件
(2)、每一個事務提交時會將重作日誌緩衝刷新的到重作日誌文件
(3)、重作日誌緩衝區空間小於1/2時,重作日誌緩衝刷新到重作日誌文件。
四、額外的內存池
對一些數據結構自己的內存分配是從額外內存池分配
3、檢查點技術
事務型數據庫通常採用Write Ahead Log策略,當事物提交時先寫redo log然後修改內存中的頁。當數據庫宕機對於還未寫入磁盤的修改數據能夠經過redo log恢復。Checkpoint做用在於保證該點以前的全部修改的頁均已刷新到磁盤,這以前的redo log在恢復數據時能夠不須要了。
檢查點的目的:
(1)、縮短數據庫的恢復時間
(2)、緩衝池不夠用時,將髒頁刷新到磁盤
(3)、重作日誌不可用時,刷新髒頁。
當數據庫宕機後,不須要重作因此的日誌。檢查點以前的頁已經刷新到磁盤,數據庫之須要對檢查點以後的重作日誌進行恢復。大大縮短了恢復時間
當緩衝池不夠用時,LRU會溢出最近少使用的頁。若是此頁爲髒頁,須要強制執行檢查點。
重作日誌不可用指的是由於當前事務性數據庫對重作日誌的設計都是循環使用的。並非無限增大,重作日誌能夠被重用指的是這些重作日誌已經再也不須要,能夠被覆蓋。若此時重作日誌還須要使用,必須強制執行檢查點。將緩衝池中的頁刷新到當前重作日誌的位置。
再InnoDB內部,檢查點有兩種
(1)、Sharp Checkpoint 發生在數據庫關閉時將全部的髒頁刷回到磁盤,是默認的工做方式。及參數innodb_fast_shutdown = 1
(2)、Fuzzy Checkpoint 在內部使用這種方式刷新,刷新一部分髒頁,不是刷新全部髒頁。
在InnoDB引擎可能發生如下幾種狀況的Fuzzy Checkpoint
(1)、Master Thread Checkpoint 在Master Thread發生的檢查點操做,差很少每秒或沒十秒從緩衝池的髒頁列表刷新必定比例的頁回磁盤,此過程是異步的。
(2)、FLUSH_LRU_LIST Chechkpoint 存儲引擎須要保證LRU隊列有空閒頁可用。Page Cleanner Thread會檢查LRU列表是否有足夠的空間來進行查詢操做。假若沒有,將LRU隊列尾端的頁移除,若是有髒也,執行檢查點。用戶經過innodb_lru_scan_death參數控制LRU列中可用頁的數量,默認值爲1024
mysql> show variables like 'innodb_lru_scan_depth'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_lru_scan_depth | 1024 | +-----------------------+-------+
(3)、Async/Sync Flush Checkpoint 指重作日誌不可用的狀況。
(4)、Dirty page too much 髒頁太多致使強制進行Checkpoint,目的仍是保證緩衝池有足夠的可用的頁。由參數innodb_max_dirty_pages_pct控制。
mysql> show variables like 'innodb_max_dirty_pages_pct'; +----------------------------+-----------+ | Variable_name | Value | +----------------------------+-----------+ | innodb_max_dirty_pages_pct | 75.000000 | +----------------------------+-----------+
當緩衝池的髒頁數量達到75%時,強制執行檢查點,刷新一部分髒頁到磁盤。
InnoDB內部協調原理:參考 https://www.cnblogs.com/janehoo/p/7717041.html
一條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線程回收。