首先以一張圖簡單展現 InnoDB 的存儲引擎的體系架構.html
從圖中可見, InnoDB 存儲引擎有多個內存塊,這些內存塊組成了一個大的內存池,主要負責以下工做:mysql
維護全部進程/線程須要訪問的多個內部數據結構算法
緩存磁盤上的數據, 方便快速讀取, 同時在對磁盤文件修改以前進行緩存sql
重作日誌(redo log)緩衝shell
後臺線程的主要做用是負責刷新內存池中的數據,保證緩衝池中的內存緩存的是最新數據;將已修改數據文件刷新到磁盤文件;保證數據庫發生異常時 InnoDB 能恢復到正常運行 的狀態數據庫
後臺線程緩存
InnoDB 使用的是多線程模型, 其後臺有多個不一樣的線程負責處理不一樣的任務服務器
這是最核心的一個線程,主要負責將緩衝池中的數據異步刷新到磁盤,保證數據的一致性,包括贓頁的刷新、合併插入緩衝、UNDO 頁的回收等.數據結構
在 InnoDB 存儲引擎中大量使用了異步 IO 來處理寫 IO 請求, IO Thread 的工做主要是負責這些 IO 請求的回調.多線程
能夠經過命令來觀察 InnoDB 中的 IO Thread:
mysql> show engine innodb status\G *************************** 1. row *************************** Type: InnoDB Name: Status: ===================================== 2017-07-22 00:16:26 7f4a37451700 INNODB MONITOR OUTPUT ===================================== -------- FILE I/O -------- I/O thread 0 state: waiting for completed aio requests (insert buffer thread) I/O thread 1 state: waiting for completed aio requests (log thread) I/O thread 2 state: waiting for completed aio requests (read thread) I/O thread 3 state: waiting for completed aio requests (read thread) I/O thread 4 state: waiting for completed aio requests (read thread) I/O thread 5 state: waiting for completed aio requests (read thread) I/O thread 6 state: waiting for completed aio requests (write thread) I/O thread 7 state: waiting for completed aio requests (write thread) I/O thread 8 state: waiting for completed aio requests (write thread) I/O thread 9 state: waiting for completed aio requests (write thread) Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] , ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0 Pending flushes (fsync) log: 0; buffer pool: 0 188 OS file reads, 27180 OS file writes, 26031 OS fsyncs 0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
能夠看到, InnoDB 共有10個 IO Thread, 分別是 4個 write、4個 read、1個 insert buffer和1個 log thread.
事務被提交以後, undo log 可能再也不須要,所以須要 Purge Thread 來回收已經使用比分配的 undo頁. InnoDB 支持多個 Purge Thread, 這樣作能夠加快 undo 頁的回收InnoDB 引擎默認設置爲1個 Purge Thread:
mysql> SHOW VARIABLES LIKE "innodb_purge_threads"; +----------------------+-------+ | Variable_name | Value | +----------------------+-------+ | innodb_purge_threads | 1 | +----------------------+-------+ 1 row in set (0.00 sec)
Page Cleaner Thread 是新引入的,其做用是將以前版本中髒頁的刷新操做都放入單獨的線程中來完成,這樣減輕了 Master Thread 的工做及對於用戶查詢線程的阻塞
InnoDB 存儲引擎是基於磁盤存儲的,其中的記錄按照頁的方式進行管理,因爲 CPU 速度和磁盤速度之間的鴻溝, InnoDB 引擎使用緩衝池技術來提升數據庫的總體性能.
緩衝池簡單來講就是一塊內存區域.在數據庫中進行讀取頁的操做,首先將從磁盤讀到的頁存放在緩衝池中,下一次讀取相同的頁時,首先判斷該頁是否是在緩衝池中,若在,稱該頁在緩衝池中被命中,直接讀取該頁.不然,讀取磁盤上的頁.
對於數據庫中頁的修改操做,首先修改在緩衝池中頁,而後再以必定的頻率刷新到磁盤,並非每次頁發生改變就刷新回磁盤.
緩衝池的大小直接影響數據庫的總體性能,對於 InnoDB 存儲引擎而言,緩衝池配置經過參數 innodb_buffer_pool_size
來設置. 下面顯示本機虛擬機上一臺 MySQL 數據庫配置:
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; +-------------------------+------------+ | Variable_name | Value | +-------------------------+------------+ | innodb_buffer_pool_size | 2147483648 | +-------------------------+------------+ 1 row in set (0.00 sec)
緩衝池中緩存的數據頁類型有:索引頁、數據頁、 undo 頁、插入緩衝、自適應哈希索引、 InnoDB 的鎖信息、數據字典信息等.索引頁和數據頁佔緩衝池的很大一部分.下圖顯示 InnoDB 存儲引擎總內存的結構狀況.
InnoDB 存儲引擎先將重作日誌信息放入這個緩衝區,而後以必定頻率將其刷新到重作日誌文件.重作日誌文件通常不須要設置得很大,由於在下列三種狀況下重作日誌緩衝中的內容會刷新到磁盤的重作日誌文件中.
Master Thread 每一秒將重作日誌緩衝刷新到重作日誌文件
每一個事物提交時會將重作日誌緩衝刷新到重作日誌文件
當重作日誌緩衝剩餘空間小於1/2時,重作日誌緩衝刷新到重作日誌文件
在 InnoDB 存儲引擎中, 對一些數據結構自己的內存進行分配時,須要從額外的內存池中進行申請.例如,分配了緩衝池,可是每一個緩衝池中的幀緩衝還有對應的緩衝控制對象,這些對象記錄以一些諸如 LRU, 鎖,等待等信息,而這個對象的內存須要從額外的內存池中申請.
01 – Undo Log
Undo Log 是爲了實現事務的原子性,在MySQL數據庫InnoDB存儲引擎中,還用Undo Log來實現多版本併發控制(簡稱:MVCC)。
- 事務的原子性(Atomicity)
事務中的全部操做,要麼所有完成,要麼不作任何操做,不能只作部分操做。若是在執行的過程當中發生了錯誤,要回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過。
- 原理
Undo Log的原理很簡單,爲了知足事務的原子性,在操做任何數據以前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲Undo Log)。而後進行數據的修改。若是出現了錯誤或者用戶執行了ROLLBACK語句,系統能夠利用Undo Log中的備份將數據恢復到事務開始以前的狀態。
除了能夠保證事務的原子性,Undo Log也能夠用來輔助完成事務的持久化。
- 事務的持久性(Durability)
事務一旦完成,該事務對數據庫所作的全部修改都會持久的保存到數據庫中。爲了保證持久性,數據庫系統會將修改後的數據徹底的記錄到持久的存儲上。
- 用Undo Log實現原子性和持久化的事務的簡化過程
假設有A、B兩個數據,值分別爲1,2。
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄B=2到undo log.
E.修改B=4.
F.將undo log寫到磁盤。
G.將數據寫到磁盤。
H.事務提交
這裏有一個隱含的前提條件:‘數據都是先讀到內存中,而後修改內存中的數據,最後將數據寫回磁盤’。
之因此能同時保證原子性和持久化,是由於如下特色:
A. 更新數據前記錄Undo log。
B. 爲了保證持久性,必須將數據在事務提交前寫到磁盤。只要事務成功提交,數據必然已經持久化。
C. Undo log必須先於數據持久化到磁盤。若是在G,H之間系統崩潰,undo log是完整的,能夠用來回滾事務。
D. 若是在A-F之間系統崩潰,由於數據沒有持久化到磁盤。因此磁盤上的數據仍是保持在事務開始前的狀態。
缺陷:每一個事務提交前將數據和Undo Log寫入磁盤,這樣會致使大量的磁盤IO,所以性能很低。
若是可以將數據緩存一段時間,就能減小IO提升性能。可是這樣就會喪失事務的持久性。所以引入了另一
種機制來實現持久化,即Redo Log.
02 – Redo Log
- 原理
和Undo Log相反,Redo Log記錄的是新數據的備份。在事務提交前,只要將Redo Log持久化便可,
不須要將數據持久化。當系統崩潰時,雖然數據沒有持久化,可是Redo Log已經持久化。系統能夠根據Redo Log的內容,將全部數據恢復到最新的狀態。
- Undo + Redo事務的簡化過程
假設有A、B兩個數據,值分別爲1,2.
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄A=3到redo log.
E.記錄B=2到undo log.
F.修改B=4.
G.記錄B=4到redo log.
H.將redo log寫入磁盤。
I.事務提交
- Undo + Redo事務的特色
A. 爲了保證持久性,必須在事務提交前將Redo Log持久化。
B. 數據不須要在事務提交前寫入磁盤,而是緩存在內存中。
C. Redo Log保證事務的持久性。
D. Undo Log保證事務的原子性。
E. 有一個隱含的特色,數據必需要晚於redo log寫入持久存儲。
- IO性能
Undo + Redo的設計主要考慮的是提高IO性能。雖然說經過緩存數據,減小了寫數據的IO. 可是卻引入了新的IO,即寫Redo Log的IO。若是Redo Log的IO性能很差,就不能起到提升性能的目的。
爲了保證Redo Log可以有比較好的IO性能,InnoDB 的 Redo Log的設計有如下幾個特色:
A. 儘可能保持Redo Log存儲在一段連續的空間上。所以在系統第一次啓動時就會將日誌文件的空間徹底分配。以順序追加的方式記錄Redo Log,經過順序IO來改善性能。
B. 批量寫入日誌。日誌並非直接寫入文件,而是先寫入redo log buffer.當須要將日誌刷新到磁盤時 (如事務提交),將許多日誌一塊兒寫入磁盤.
C. 併發的事務共享Redo Log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一塊兒,以減小日誌佔用的空間。例如,Redo Log中的記錄內容多是這樣的:
記錄1: <trx1, insert …>
記錄2: <trx2, update …>
記錄3: <trx1, delete …>
記錄4: <trx3, update …>
記錄5: <trx2, insert …>
D. 由於C的緣由,當一個事務將Redo Log寫入磁盤時,也會將其餘未提交的事務的日誌寫入磁盤。
E. Redo Log上只進行順序追加的操做,當一個事務須要回滾時,它的Redo Log記錄也不會從Redo Log中刪除掉。
03 – 恢復(Recovery)
- 恢復策略
前面說到未提交的事務和回滾了的事務也會記錄Redo Log,所以在進行恢復時,這些事務要進行特殊的的處理.有2中不一樣的恢復策略:
A. 進行恢復時,只重作已經提交了的事務。
B. 進行恢復時,重作全部事務包括未提交的事務和回滾了的事務。而後經過Undo Log回滾那些未提交的事務。
- InnoDB存儲引擎的恢復機制
MySQL數據庫InnoDB存儲引擎使用了B策略, InnoDB存儲引擎中的恢復機制有幾個特色:
A. 在重作Redo Log時,並不關心事務性。 恢復時,沒有BEGIN,也沒有COMMIT,ROLLBACK的行爲。也不關心每一個日誌是哪一個事務的。儘管事務ID等事務相關的內容會記入Redo Log,這些內容只是被看成要操做的數據的一部分。
B. 使用B策略就必需要將Undo Log持久化,並且必需要在寫Redo Log以前將對應的Undo Log寫入磁盤。
Undo和Redo Log的這種關聯,使得持久化變得複雜起來。爲了下降複雜度,InnoDB將Undo Log看做數據,所以記錄Undo Log的操做也會記錄到redo log中。這樣undo log就能夠象數據同樣緩存起來, 而不用在redo log以前寫入磁盤了。
包含Undo Log操做的Redo Log,看起來是這樣的
記錄1: <trx1, Undo log insert <undo_insert …>>
記錄2: <trx1, insert …>
記錄3: <trx2, Undo log insert <undo_update …>>
記錄4: <trx2, update …>
記錄5: <trx3, Undo log insert <undo_delete …>>
記錄6: <trx3, delete …>
C. 到這裏,還有一個問題沒有弄清楚。既然Redo沒有事務性,那豈不是會從新執行被回滾了的事務?
確實是這樣。同時Innodb也會將事務回滾時的操做也記錄到redo log中。回滾操做本質上也是對數據進行修改,所以回滾時對數據的操做也會記錄到Redo Log中。
一個回滾了的事務的Redo Log,看起來是這樣的:
記錄1: <trx1, Undo log insert <undo_insert …>>
記錄2: <trx1, insert A…>
記錄3: <trx1, Undo log insert <undo_update …>>
記錄4: <trx1, update B…>
記錄5: <trx1, Undo log insert <undo_delete …>>
記錄6: <trx1, delete C…>
記錄7: <trx1, insert C>
記錄8: <trx1, update B to old value>
記錄9: <trx1, delete A>
一個被回滾了的事務在恢復時的操做就是先redo再undo,所以不會破壞數據的一致性.
InnoDB維護一個稱爲緩衝池的內存存儲區域 ,用於緩存內存中的數據和索引。瞭解InnoDB緩衝池的工做原理,並利用它來保存內存中常常訪問的數據,這是MySQL調優的一個重要方面。
1.1 LRU(least recently used)
InnoDB將buffer pool做爲一個list管理,基於LRU算法。當有新的頁要讀入到buffer pool的時候,buffer pool就將最近最少使用的頁從buffer pool中驅逐出去,而且將新頁加入到list的中間位置,這就是所謂的「中點插入策略」。通常狀況下list頭部存放的是熱數據,就是所謂的young pages(最近常常訪問的數據),list尾部存放的就是old pages(最近不被訪問的數據)。這個算法就保證了最近常用的page信息會被保存在最近訪問的sublist中,相反的不被常常訪問的就會保存在old sublist,當新數據寫入的時候被old sublist中的page信息會被首先驅逐的。
LRU算法有如下的標準算法:
1)3/8的list信息是做爲old list,這些信息是被驅逐的對象。
2)list的中點就是咱們所謂的old list頭部和new list尾部的鏈接點,至關於一個界限。
3)新數據的讀入首先會插入到old list的頭部。
4)若是是old list的數據被訪問到了,這個頁信息就會變成new list,變成young page,就會將數據頁信息移動到new sublist的頭部。
5)在數據庫的buffer pool裏面,無論是new sublist仍是old sublist的數據若是不會被訪問到,最後都會被移動到list的尾部做爲犧牲者。
通常狀況下,頁信息會被查詢語句立馬查詢到而被移動到new sublist,這就意味着他們會在buffer pool裏面保留很長一段時間。表掃描(包括mysqldump或者沒有where條件的select等操做)等操做將會刷入大量的數據進入buffer pool,同時也會將更多的buffer pool當中的信息刷出去,即便這個操做可能只會使用到一次而已。一樣的,若是read-ahead(線性預讀)後臺進程讀入大量數據的狀況下也是會形成buffer pool大量高頻的刷新數據頁,可是這些操做是可控的,下面3,4會說獲得。read-ahead操做簡單說一下就是MySQL的一個後臺預讀進程,可以保證MySQL預讀入數據進入buffer pool當中。
當你作backup或者report的時候,能夠頻繁的往buffer pool裏面讀取數據,不用有太多的顧慮。
InnoDB採用的是一種不是像LRU那麼嚴格的方法來保證將最近訪問的數據寫入到buffer pool裏面,而且最大可能的下降減小數據的帶入量。這個語句是全表掃描或者之後這個數據將不會再被訪問到,可是緩衝數據仍是會寫入到buffer pool裏面。
新寫入的數據會被插入到LRU list的中間位置,默認會插入到從list尾部算起來的3/8的位置,當這些寫入的數據在buffer pool中被第一次訪問的時候,在list中的位置就會向前移動,這樣其實就會在list保留兩個位置,老的位置並不會被當即清除,直到老的LRU list的位置被標記爲OLD的時候,纔會在下一次插入數據的時候被做爲犧牲者清除掉。
咱們自己是能夠指定插入LRU list的位置,而且也能夠設置當索引掃描或者是全表掃描的時候是否是採用這個相同的優化方法。innodb_old_blocks_pct這個參數設置的是插入的位置,默認的值是37,咱們能夠設置的值是5-95之間,其他部分並不用來保存熱數據。可是還有一個嚴重的問題就是當一個全表掃描或者索引的掃描常常被訪問的時候,就會存儲很大的數據到buffer pool裏面,咱們都知道這是很危險的一件事。因此MySQL給咱們如下參數來設置保留在buffer pool裏面的數據在插入時候沒有被改變list位置的時候的保存時間innodb_old_blocks_time,單位是毫秒,這個值的默認值是1000。若是增大這個值的話,就會讓buffer pool裏面不少頁信息變老的速度變快,這個很好理解把,由於這些數據不會很快被內存中擦除的話,就會變成熱數據而擠掉原有緩存的數據。
以上的兩個參數都是能夠動態設置的,固然也能夠在my.cnf裏面設置。固然設置這些前必定要對機器配置,表信息,負載狀況有充分的瞭解才能進行設置,生產庫儘可能不要隨便修改。若是OLTP系統中有大量的大查詢的話,設置innodb_old_blocks_time可以較大的提供系統的穩定性。若是當一個大查詢很大不足夠存儲到buffer pool當中的時候,咱們能夠指定innodb_old_blocks_pct的值小一點,以保證這些數據只會被讀取一次,好比說設置爲5的時候,就限制了一次讀取數據最多隻能被讀取到buffer pool當中5%。固然一些小表而且是常常訪問到的數據的話就能夠適當設置較大的值,好比50。固然設置這兩個值的時候必定要創建在你充分了解你的數據負載的基礎上,否則千萬不要亂改。
2.2 Buffer Pool
InnoDB緩衝池將表的索引和數據進行緩存,緩衝池容許從內存直接處理頻繁使用的數據,這加快了處理速度。在專用數據庫服務器上,一般將多達80%的物理內存分配給InnoDB緩衝池。由於InnoDB的存儲引擎的工做方式老是將數據庫文件按頁讀取到緩衝池,每一個頁16k默認(innodb_page_size=16k),在MySQL 5.7中增長了32KB和64KB頁面大小的支持,以前版本是不容許大於16k的;但你只能在初始化MySQL實例以前進行配置,一旦設置了一個實例的頁面大小,就不能改變它,具體看innodb_page_size參數。
而後按最近最少使用(LRU)算法來保留在緩衝池中的緩存數據。若是數據庫文件須要修改,老是首先修改在緩存池中的頁(發生修改後,該也即爲髒也),而後再按照必定的頻率將緩衝池的髒也刷新到文件中。能夠經過show engine innodb status來查看innodb_buffer_pool的具體使用狀況(默認是8個緩衝池實例),以下:
mysql> show engine innodb status\G Per second averages calculated from the last 38 seconds(如下信息來之過去的38秒) ---------------------- BUFFER POOL AND MEMORY ---------------------- Total memory allocated 1098907648; in additional pool allocated 0 Dictionary memory allocated 59957 Buffer pool size 65536 Free buffers 65371 Database pages 165 Old database pages 3 Modified db pages 9 ..........
在Buffer pool size中能夠看到內存池的使用狀況:
Total memory allocated:爲緩衝池分配的總內存(以字節爲單位)。
Dictionary memory allocated:分配給InnoDB數據字典的總內存(以字節爲單位)。
Buffer pool size:分配給緩衝池的頁面總數量(數量*頁面大小=緩衝池大小),默認每一個Page爲16k。
Free buffers:緩衝池空閒列表的頁面總數量(Buffer pool size -Database pages)。
Database pages:緩衝池LRU LIST的頁面總數量(能夠理解爲已經使用的頁面)。
Old database pages:緩衝池舊LRU SUBLIST的頁面總大小(能夠理解爲不常常訪問的頁面,即將可能被LRU算法淘汰的頁面)。
Modified db pages:在緩衝池中已經修改了的頁數,所謂髒數據。
因此這裏一共分配了63336*16/1024=1G內存的緩衝池,空閒65371個頁面,已經使用了165個頁面,不常常修改的數據頁有3個(通常佔用內存的1/3),髒頁的頁面有2個,這些數據能分析當前數據庫的壓力值。
你能夠配置InnoDB緩衝池的各個方面來提升性能。
2.1 在線配置InnoDB緩衝池大小
緩衝池支持脫機和聯機兩種配置方式,當增長或減小innodb_buffer_pool_size時,操做以塊(chunk)形式執行。塊大小由innodb_buffer_pool_chunk_size配置選項定義,默認值128M。
在線配置InnoDB緩衝池大小,該innodb_buffer_pool_size配置選項能夠動態使用設置SET聲明,讓你調整緩衝池無需從新啓動服務器。例如:
mysql> SET GLOBAL innodb_buffer_pool_size=8589934592;
緩衝池大小配置必須始終等於innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的倍數。若是配置innodb_buffer_pool_size爲不等於innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的倍數,則緩衝池大小將自動調整爲等於或不小於指定緩衝池大小的innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的倍數。
在如下示例中, innodb_buffer_pool_size設置爲8G,innodb_buffer_pool_instances設置爲16,innodb_buffer_pool_chunk_size是128M,這是默認值。8G是一個有效的innodb_buffer_pool_size值,由於它是innodb_buffer_pool_instances=16乘以innodb_buffer_pool_chunk_size=128M的倍數。
mysql> select 8*1024 / (16*128); +-------------------+ | 8*1024 / (16*128) | +-------------------+ | 4.0000 | +-------------------+ 1 row in set (0.00 sec)
若是innodb_buffer_pool_size設置爲9G,innodb_buffer_pool_instances設置爲16,innodb_buffer_pool_chunk_size是128M,這是默認值。在這種狀況下,9G不是innodb_buffer_pool_instances=16*innodb_buffer_pool_chunk_size=128M的倍數 ,因此innodb_buffer_pool_size被調整爲10G,這是不小於指定緩衝池大小的下一個innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的倍數。
2.2 監控在線緩衝池調整大小進度
該Innodb_buffer_pool_resize_status報告緩衝池大小調整的進展。例如:
mysql> SHOW STATUS WHERE Variable_name ='InnoDB_buffer_pool_resize_status'; +----------------------------------+-------+ | Variable_name | Value | +----------------------------------+-------+ | Innodb_buffer_pool_resize_status | | +----------------------------------+-------+ 1 row in set (0.01 sec)
2.3 配置InnoDB緩衝池塊(chunk)大小
innodb_buffer_pool_chunk_size能夠在1MB(1048576字節)單位中增長或減小,但只能在啓動時,在命令行字符串或MySQL配置文件中進行修改。
[mysqld] innodb_buffer_pool_chunk_size = 134217728
修改innodb_buffer_pool_chunk_size時適用如下條件:
例如,若是緩衝池初始化大小爲2GB(2147483648字節), 4個緩衝池實例和塊大小1GB(1073741824字節),則塊大小將被截斷爲等於innodb_buffer_pool_size / innodb_buffer_pool_instances,值爲:
mysql> select 2147483648 / 4; +----------------+ | 2147483648 / 4 | +----------------+ | 536870912.0000 | +----------------+ 1 row in set (0.00 sec)
更改時應當心innodb_buffer_pool_chunk_size,由於更改此值能夠增長緩衝池的大小,如上面的示例所示。在更改innodb_buffer_pool_chunk_size以前,計算innodb_buffer_pool_size以確保生成的緩衝池大小是可接受的。
2.4 在線調整緩衝池內部大小機制
調整大小的操做由後臺線程執行,當增長緩衝池的大小時,調整大小操做:
PS:當這些操做正在進行時,阻止其餘線程訪問緩衝池。
當減少緩衝池的大小時,調整大小操做:
在這些操做中,只有對緩衝池進行碎片整理和撤銷頁面才容許其餘線程同時訪問緩衝池。
InnoDB在調整緩衝池大小以前,應完成經過API執行的活動事務和操做。啓動調整大小操做時,在全部活動事務完成以前,操做都不會啓動。一旦調整大小操做進行中,須要訪問緩衝池的新事務和操做必須等到調整大小操做完成,可是容許在緩衝池進行碎片整理時緩衝池的併發訪問。緩衝池大小減小時頁面被撤銷,容許併發訪問的一個缺點是在頁面被撤回時可能會致使可用頁面暫時不足。
對於具備多GB級緩衝池的系統,將緩衝池劃分爲單獨的實例能夠經過減小不一樣線程讀取和寫入緩存頁面的爭用來提升併發性。此功能一般適用於緩衝池大小在千兆字節範圍內的系統。使用innodb_buffer_pool_instances配置選項配置多個緩衝池實例,你也能夠調整該 innodb_buffer_pool_size值。
當InnoDB緩衝池大時,能夠經過從內存檢索來知足許多數據請求。你可能會遇到多個請求一次訪問緩衝池的線程的瓶頸。你能夠啓用多個緩衝池以最小化此爭用。使用散列函數,將緩衝池中存儲或讀取的每一個頁面隨機分配給其中一個緩衝池。每一個緩衝池管理本身的空閒列表,刷新列表,LRU和鏈接到緩衝池的全部其餘數據結構,並由其本身的緩衝池互斥鎖保護。
要啓用多個緩衝池實例,請將innodb_buffer_pool_instances配置選項設置爲大於1(默認值)高達64(最大值)的值。此選項僅在設置innodb_buffer_pool_size爲1GB或更大的大小時生效。你指定的總大小在全部緩衝池之間分配,爲了得到最佳效率,指定的組合innodb_buffer_pool_instances和innodb_buffer_pool_size,使得每一個緩衝池實例是至少爲1GB。
InnoDB在io的優化上有個比較重要的特性爲預讀,預讀請求是一個i/o請求,它會異步地在緩衝池中預先回遷多個頁面,預計很快就會須要這些頁面,這些請求在一個範圍內引入全部頁面。InnoDB以64個page爲一個extent,那麼InnoDB的預讀是以page爲單位仍是以extent?
這樣就進入了下面的話題,InnoDB使用兩種預讀算法來提升I/O性能:線性預讀(linear read-ahead)和隨機預讀(randomread-ahead)
爲了區分這兩種預讀的方式,咱們能夠把線性預讀放到以extent爲單位,而隨機預讀放到以extent中的page爲單位。線性預讀着眼於將下一個extent提早讀取到buffer pool中,而隨機預讀着眼於將當前extent中的剩餘的page提早讀取到buffer pool中。
線性預讀(linear read-ahead):它能夠根據順序訪問緩衝池中的頁面,預測哪些頁面可能須要很快。經過使用配置參數innodb_read_ahead_threshold,經過調整觸發異步讀取請求所需的順序頁訪問數,能夠控制Innodb執行提早讀操做的時間。在添加此參數以前,InnoDB只會計算當在當前範圍的最後一頁中讀取整個下一個區段時是否發出異步預取請求。
線性預讀方式有一個很重要的變量控制是否將下一個extent預讀到buffer pool中,經過使用配置參數innodb_read_ahead_threshold,能夠控制Innodb執行預讀操做的時間。若是一個extent中的被順序讀取的page超過或者等於該參數變量時,Innodb將會異步的將下一個extent讀取到buffer pool中,innodb_read_ahead_threshold能夠設置爲0-64的任何值,默認值爲56,值越高,訪問模式檢查越嚴格。
mysql> show global variables like '%innodb_read_ahead_threshold%'; +-----------------------------+-------+ | Variable_name | Value | +-----------------------------+-------+ | innodb_read_ahead_threshold | 56 | +-----------------------------+-------+ 1 row in set (0.00 sec)
例如,若是將值設置爲48,則InnoDB只有在順序訪問當前extent中的48個pages時才觸發線性預讀請求,將下一個extent讀到內存中。若是值爲8,InnoDB觸發異步預讀,即便程序段中只有8頁被順序訪問。你能夠在MySQL配置文件中設置此參數的值,或者使用SET GLOBAL須要該SUPER權限的命令動態更改該參數。
在沒有該變量以前,當訪問到extent的最後一個page的時候,Innodb會決定是否將下一個extent放入到buffer pool中。
隨機預讀(randomread-ahead):隨機預讀方式則是表示當同一個extent中的一些page在buffer pool中發現時,Innodb會將該extent中的剩餘page一併讀到buffer pool中,因爲隨機預讀方式給Innodb code帶來了一些沒必要要的複雜性,同時在性能也存在不穩定性,在5.5中已經將這種預讀方式廢棄。要啓用此功能,請將配置變量設置innodb_random_read_ahead爲ON。
mysql> show global variables like '%innodb_random_read_ahead%'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_random_read_ahead | OFF | +--------------------------+-------+ 1 row in set (0.01 sec)
在監控Innodb的預讀時候,咱們能夠經過SHOW ENGINE INNODB STATUS命令顯示統計信息,經過Pages read ahead和evicted without access兩個值來觀察預讀的狀況,或者經過兩個狀態值,以幫助您評估預讀算法的有效性。
mysql> show global status like '%Innodb_buffer_pool_read_ahead%'; +---------------------------------------+-------+ | Variable_name | Value | +---------------------------------------+-------+ | Innodb_buffer_pool_read_ahead_rnd | 0 | | Innodb_buffer_pool_read_ahead | 0 | | Innodb_buffer_pool_read_ahead_evicted | 0 | +---------------------------------------+-------+ 3 rows in set (0.00 sec)
而經過SHOW ENGINE INNODB STATUS獲得的Pages read ahead和evicted without access則表示每秒讀入和讀出的pages:Pages read ahead 1.00/s, evicted without access 9.99/s。
當微調innodb_random_read_ahead設置時,此信息可能頗有用 。
InnoDB會在後臺執行某些任務,包括從緩衝池刷新髒頁(那些已更改但還沒有寫入數據庫文件的頁)。
InnoDB當緩衝池中髒頁的百分比達到定義的低水位設置時,其實就是當緩衝池中的髒頁佔用比達到innodb_max_dirty_pages_pct_lwm的設定值的時候,就會自動將髒頁清出buffer pool,這是爲了保證buffer pool當中髒頁的佔有率,也是爲了防止髒頁佔有率超過innodb_max_dirty_pages_pct的設定值,當髒頁的佔有率達到了innodb_max_dirty_pages_pct的設定值的時候,InnoDB就會強制刷新buffer pool pages。
InnoDB採用一種基於redo log的最近生成量和最近刷新頻率的算法來決定沖洗速度,這樣的算法能夠保證數據庫的沖洗不會影響到數據庫的性能,也能保證數據庫buffer pool中的數據的髒數據的佔用比。這種自動調整刷新速率有助於避免過多的緩衝池刷新限制了普通讀寫請求可用的I/O容量,從而避免吞吐量忽然降低,但仍是對正常IO有影響。
咱們知道InnoDB使用日誌的方式是循環使用的,在重用前一個日誌文件以前,InnoDB就會將這個日誌這個日誌記錄相關的全部在buffer pool當中的數據刷新到磁盤,也就是所謂的sharp checkpoint,和sqlserver的checkpoint很像。當一個插入語句產生大量的redo信息須要記錄的日誌,當前redo log文件不可以徹底存儲,也會寫入到當前的redo文件當中。當redo log當中的全部使用空間都被用完了的,就會觸發sharp checkpoint,因此這個時候即便髒數據佔有率沒有達到innodb_max_dirty_pages_pct,仍是會進行刷新。
內部基準測試顯示,該算法隨着時間的推移能夠顯著提升總體吞吐量。這種算法是經得住考驗的,因此說千萬不要隨便設置,最好是默認值。可是咱們從中也就會知道爲何redo log不可以記錄兩個事物的redo信息了。由於有這麼多的好處,因此innodb_adaptive_flushing的值默認就是true的,默認開啓自適應刷新策略。
配置選項innodb_flush_neighbors, innodb_lru_scan_depth可讓你微調緩衝池刷新過程的某些方面,這些選項主要是幫助寫密集型的工做負載。若是DML操做較爲嚴重,若是沒有較高的值,則刷新可能會降低,會致使緩衝池中的內存過多。或者,若是這種機制過於激進,磁盤寫入將會使你的I/O容量飽和,理想的設置取決於你的工做負載,數據訪問模式和存儲配置(例如數據是否存儲在HDD或SSD設備上)。
InnoDB對於具備不斷繁重工做負載的系統或者工做負載波動很大的系統,可使用下面幾個配置選項來調整表的刷新行爲:
上面提到的大多數選項最適用於長時間運行寫入繁重工做負載的服務器。
7.1 在關閉時保存緩衝池狀態並在啓動時恢復緩衝池狀態
能夠配置在MySQL關閉以前,保存InnoDB當前的緩衝池的狀態,以免在服務器從新啓動後,還要經歷一個預熱的暖機時間。經過innodb_buffer_pool_dump_at_shutdown(服務器關閉前設置)來設置,當設置這個參數之後MySQL就會在機器關閉時保存InnoDB當前的狀態信息到磁盤上。
當啓動MySQL服務器時要恢復服務器緩衝池狀態,請在啓動服務器時開啓innodb_buffer_pool_load_at_startup參數。我的認爲這個值仍是須要配置一下的,MySQL 5.7.6版本以前這兩個值默認是關閉的,但從MySQL 5.7.7版本開始這兩個值就默認爲開啓狀態了。這些數據是從磁盤從新讀取到buffer pool當中的,這會花費一些時間,而且恢復時新的DML操做是不可以進行操做的。這些數據是怎麼恢復呢?其實INNODB_BUFFER_PAGE_LRU表(INFORMATION_SCHEMA)會記錄緩存的tablespace ID和page ID,經過這個來恢復。另外緩衝池狀態保存文件默認在數據目錄下,名爲」ib_buffer_pool」,可使用innodb_buffer_pool_filename參數來修改文件名和位置。
7.2 配置緩衝池頁面保存的百分比
在加載數據進入buffer pool以前,能夠經過設置innodb_buffer_pool_dump_pct參數來決定恢復buffer pool中多少數據。MySQL 5.7.6版本以前的默認值是100,恢復所有,從MySQL 5.7.7版本以後默認調整爲25了。能夠動態設置此參數:
mysql> SET GLOBAL innodb_buffer_pool_dump_pct = 40;
7.3 在線保存和恢復緩衝池狀態
要在運行MySQL服務器時保存緩衝池的狀態,請發出如下語句:
mysql> SET GLOBAL innodb_buffer_pool_dump_now=ON;
要在MySQL運行時恢復緩衝池狀態,請發出如下語句:
mysql> SET GLOBAL innodb_buffer_pool_load_now = ON;
若是要終止buffer pool加載,能夠指定運行:
mysql> SET GLOBAL innodb_buffer_pool_load_abort=ON;
7.4 顯示緩衝池保存和加載進度
要想顯示將緩衝池狀態保存到磁盤時的進度,請發出如下語句:
mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status'; +--------------------------------+------------------------------------+ | Variable_name | Value | +--------------------------------+------------------------------------+ | Innodb_buffer_pool_dump_status | Dumping of buffer pool not started | +--------------------------------+------------------------------------+ 1 row in set (0.03 sec)
要想顯示加載緩衝池時的進度,請發出如下語句:
mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_load_status'; +--------------------------------+--------------------------------------------------+ | Variable_name | Value | +--------------------------------+--------------------------------------------------+ | Innodb_buffer_pool_load_status | Buffer pool(s) load completed at 170428 16:13:21 | +--------------------------------+--------------------------------------------------+ 1 row in set (0.00 sec)
並且咱們能夠經過innodb的performance schema監控buffer pool的LOAD狀態,打開或者關閉stage/innodb/buffer pool load。
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'stage/innodb/buffer%';
啓動events_stages_current,events_stages_history,events_stages_history_long表監控。
mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%stages%';
經過啓用保存當前的緩衝池狀態來獲取最近的buffer pool狀態。
mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status'\G *************************** 1. row *************************** Variable_name: Innodb_buffer_pool_dump_status Value: Buffer pool(s) dump completed at 170525 18:41:06 1 row in set (0.01 sec)
經過啓用恢復當前的緩衝池狀態來獲取最近加載到buffer pool狀態。
mysql> SET GLOBAL innodb_buffer_pool_load_now=ON; Query OK, 0 rows affected (0.00 sec)
經過查詢性能模式events_stages_current表來檢查緩衝池加載操做的當前狀態,該WORK_COMPLETED列顯示加載的緩衝池頁數,該WORK_ESTIMATED列提供剩餘工做的估計,以頁爲單位。
mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM performance_schema.events_stages_current; +-------------------------------+----------------+----------------+ | EVENT_NAME | WORK_COMPLETED | WORK_ESTIMATED | +-------------------------------+----------------+----------------+ | stage/innodb/buffer pool load | 5353 | 7167 | +-------------------------------+----------------+----------------+
若是緩衝池加載操做已經完成,該表將返回一個空集合。在這種狀況下,你能夠檢查events_stages_history表以查看已完成事件的數據。例如:
mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM performance_schema.events_stages_history; +-------------------------------+----------------+----------------+ | EVENT_NAME | WORK_COMPLETED | WORK_ESTIMATED | +-------------------------------+----------------+----------------+ | stage/innodb/buffer pool load | 7167 | 7167 | +-------------------------------+----------------+----------------+
須要留意的一點是若是是壓縮表的話,在讀取到buffer pool的時候仍是會保持壓縮的格式,直到被讀取的時候纔會調用解壓程序進行解壓。
MySQL 5.7.18版本相關參數的默認值以下:
# MySQL 5.7.18; mysql> show global variables like '%innodb_buffer_pool%'; +-------------------------------------+----------------+ | Variable_name | Value | +-------------------------------------+----------------+ | innodb_buffer_pool_chunk_size | 134217728 | | innodb_buffer_pool_dump_at_shutdown | ON | | innodb_buffer_pool_dump_now | OFF | | innodb_buffer_pool_dump_pct | 25 | | innodb_buffer_pool_filename | ib_buffer_pool | | innodb_buffer_pool_instances | 1 | | innodb_buffer_pool_load_abort | OFF | | innodb_buffer_pool_load_at_startup | ON | | innodb_buffer_pool_load_now | OFF | | innodb_buffer_pool_size | 134217728 | +-------------------------------------+----------------+ 10 rows in set (0.00 sec)
檢查點的工做機制:
innodb會自動維護一個檢查點的機制,叫作 fuzzy checkpointing(固然sharp checkpoint也是檢查點之一),fuzzy checkpointing就是將buffer pool當中的數據頁信息小批量的刷新到磁盤。可是咱們沒有必要單批次批次的對buffer pool進行刷新,否則後影響其餘正在執行的SQL進程。
在crash recovery期間,MySQL也會記錄一次檢查點信息到log file當中去。它會記錄數據庫檢查點發生以前的全部修改數據庫的操做,這樣數據庫就會在日誌文件當中查找檢查點信息,而後往前讀日誌從新執行(前滾)。
頁的修改信息通常都會被記錄到buffer pool當中,稍後這些信息就會被刷新到磁盤的數據文件當中,flushing後臺進程來負責處理這個事情。所謂的檢查點就是記錄最後一次修改寫入磁盤數據文件的一個記錄信息(具體的表現形式就是LSN)。
下面稍微簡單的瞭解一下和檢查點相關的MySQL的進程和機制:
fuzzy checkpointing:一個後臺進程,按期刷新buffer pool當中一部分的dirty page到磁盤當中。
sharp checkpoint:一次性將buffer pool當中的全部髒頁刷新到磁盤數據文件,在MySQL重用日誌文件以前發生。因爲MySQL的日誌文件是循環利用的,因此一般較高的負載的狀況下會頻繁發生。
adaptive flushing:經過引發檢查點來減輕IO負擔的一種算法,取代了一次刷新全部髒頁,adaptive flushing每次只刷新一部分髒頁落盤,這個算法會根據數據沖洗的速度和頻率自動算出最優的刷新週期。
flush:將更改刷新到數據文件,也就是所謂的落盤。在INNODB的存儲結構當中,按期刷新的有redo log,undo log和buffer pool等。可是flush何時會發生呢?一種狀況是MySQL內存存儲區域已經滿了的時候會觸動發生flush,由於新的改變發生的話就會須要新的buffer pool空間來保存信息。若是不是當即須要刷新全部的buffer pool信息到磁盤的話,通常狀況下將會使用fuzzy checkpointing這個進程一點一點來處理。
看了這麼多,到底檢查點是如何工做的呢?下面大致的看一下:
關於INNODB checkpoint的算法並無太多的文檔記載,由於理解起來很難,並且還要去理解不少INNODB的不少其餘相關的東西才能夠很好的幫助你理解checkpoint。
首先咱們要知道的就是檢查點分爲兩種,一種是sharp checkpoint, 另一種就是 fuzzy checkpoint。
如上面介紹的,sharp checkpoint一次性將buffer pool當中的全部髒頁刷新到磁盤數據文件。而且記錄LSN(log sequence number )到最後一個提交的事物的位置。固然,沒有提交的事物是不會被刷新到磁盤當中的。這點和sqlserver仍是有點不同的,sqlserver是會將提交和未提交的都給刷新到磁盤當中去,這樣看起來就違反了預寫日誌的規則。恢復之後,REDO LOG就會從最後一個LSN開始,也就是檢查點發生的位置。sharp checkpoint將全部的數據刷新到磁盤當中去都是基於一個時間點的,這個LSN就是所謂的檢查點發生的位置。
fuzzy checkpoint就更加複雜了,它是在固定的時間點發生,除非他已經將全部的頁信息刷新到了磁盤,或者是剛發生過一次sharp checkpoint,fuzzy checkpoint發生的時候會記錄兩次LSN,也就是檢查點發生的時間和檢查點結束的時間。可是呢,被刷新的頁在並不必定在某一個時間點是一致的,這也就是它爲何叫fuzzy的緣由。較早刷入磁盤的數據可能已經修改了,較晚刷新的數據可能會有一個比前面LSN更新更小的一個LSN。fuzzy checkpoint在某種意義上能夠理解爲fuzzy checkpoint從redo log的第一個LSN執行到最後一個LSN。恢復之後的話,REDO LOG就會從最後一個檢查點開始時候記錄的LSN開始。
通常狀況下你們可能fuzzy checkpoint的發生頻率會遠高於sharp checkpoint發生的頻率,這個事毫無疑問的。不過當數據庫關閉,切換redo 日誌文件的時候是會觸發sharp checkpoint,通常狀況是fuzzy checkpoint發生的更多一些。
通常狀況下,執行普通操做的時候將不會發生檢查點的操做,可是,fuzzy checkpoint卻要根據時間推動而不停的發生。刷新髒頁已經成爲了數據庫的一個普通的平常操做。
INNODB維護了一個大的緩衝區,以保證被修改的數據不會被當即寫入磁盤。她會將這些修改過的數據先保留在buffer pool當中,這樣在這些數據被寫入磁盤之前可能會通過屢次的修改,咱們稱之爲寫結合。這些數據頁在buffer pool當中都是按照list來管理的,free list會記錄那些空間是可用的,LRU list記錄了那些數據頁是最近被訪問到的。flush list則記錄了在LSN順序當中的全部的dirty page信息,最近最少修改信息。
這裏着重看一下flush list,咱們知道innodb的緩存空間是有限的。若是buffer pool空間使用完畢,再次讀取新數據就會發生磁盤讀,也就是會發生flush操做,因此說就要釋放一部分沒有被使用的空間來保證buffer pool的可用性。因爲這樣的操做是很耗時的,因此說INNODB是會連續按照時間點去執行刷新操做,這樣就保證了又足夠的clean page來做爲交換,而沒必要發生flush操做。每一次刷新都會將flush list的最老的信息驅逐,這樣纔可以保證數據庫緩衝命中率是很高的一個值。這些老數據的選取是根據他們在磁盤的位置和LSN(最後一次修改的)號來確認數據新舊。
MySQL數據的日誌都是混合循環使用的,可是若是這些事物記錄的頁信息尚未被刷新到磁盤當中的話是絕對不會被覆蓋寫入的。若是還沒被刷新入磁盤的數據被覆蓋了日誌文件,那數據庫宕機的話豈不是全部被覆蓋寫入的事物對應的數據都要丟失了呢。所以,數據修改也是有時間限制的,由於新的事物或者正在執行的事物也是須要日誌空間的。日誌越大,限制就越小。並且每次fuzzy checkpoint都會將最老最不被訪問的數據驅逐出去,這也保證了每次驅逐的都是最老的數據,在下第二天志被覆蓋寫入的時候都是已經被刷盤的數據的日誌信息。最後一個老的,不被訪問的數據的事物的LSN就是事務日誌的 low-water標記,INNODB一直想提升這個LSN的值以保證buffer pool又足夠的空間刷入新的數據,同時保證了數據庫事務日誌文件能夠被覆蓋寫入的時候有足夠的空間使用。將事務日誌設置的大一些可以下降釋放日誌空間的緊迫性,從而能夠大大的提升性能。
當innodb刷新 dirty page落盤的時候,他會找到最老的dirty page對應的LSN而且將其標記爲low-water,而後將這些信息記錄到事物日誌的頭部,所以,每次刷新髒頁都是要從flush list的頭部進行刷新的。在推動最老的LSN的標記位置的時候,本質上就是作了一次檢查點。
當INNODB宕機的時候,他還要作一些額外的操做,第一:中止全部的數據更新等操做,第二:將dirty page in buffer 的數據刷新落盤,第三:記錄最後的LSN,由於咱們上面也說到了,此次發生的是sharp checkpoint,而且,這個LSN會寫入到沒一個數據庫文件的頭部,以此來標記最後發生檢查點的時候的LSN位置。
咱們知道,刷新髒頁數據的頻率若是越高的話就表明整個數據庫的負載很大,越小固然表明數據庫的壓力會小一點。將LOG 文件設置的很大可以再檢查點發生期間減小磁盤的IO,總大小最好可以設置爲和buffer pool大小相同,固然若是日誌文件設置太大的話MySQL就會再crash recovery的時候花費更多的時間(5.5以前)。
思考一下這個場景:若是重作日誌能夠無限地增大,同時緩衝池也足夠大,那麼是不須要將緩衝池中頁的新版本刷新回磁盤。由於當發生宕機時,徹底能夠經過重作日誌來恢復整個數據庫系統中的數據到宕機發生的時刻。
可是這須要兩個前提條件:一、緩衝池能夠緩存數據庫中全部的數據;二、重作日誌能夠無限增大
所以Checkpoint(檢查點)技術就誕生了,目的是解決如下幾個問題:一、縮短數據庫的恢復時間;二、緩衝池不夠用時,將髒頁刷新到磁盤;三、重作日誌不可用時,刷新髒頁。
當數據庫發生宕機時,數據庫不須要重作全部的日誌,由於Checkpoint以前的頁都已經刷新回磁盤。數據庫只需對Checkpoint後的重作日誌進行恢復,這樣就大大縮短了恢復的時間。
當緩衝池不夠用時,根據LRU算法會溢出最近最少使用的頁,若此頁爲髒頁,那麼須要強制執行Checkpoint,將髒頁也就是頁的新版本刷回磁盤。
當重作日誌出現不可用時,由於當前事務數據庫系統對重作日誌的設計都是循環使用的,並非讓其無限增大的,重作日誌能夠被重用的部分是指這些重作日誌已經再也不須要,當數據庫發生宕機時,數據庫恢復操做不須要這部分的重作日誌,所以這部分就能夠被覆蓋重用。若是重作日誌還須要使用,那麼必須強制Checkpoint,將緩衝池中的頁至少刷新到當前重作日誌的位置。
對於InnoDB存儲引擎而言,是經過LSN(Log Sequence Number)來標記版本的。
LSN是8字節的數字,每一個頁有LSN,重作日誌中也有LSN,Checkpoint也有LSN。能夠經過命令SHOW ENGINE INNODB STATUS來觀察:
mysql> show engine innodb status \G --- LOG --- Log sequence number 34778380870 Log flushed up to 34778380870 Last checkpoint at 34778380870 0 pending log writes, 0 pending chkp writes 54020151 log i/o's done, 0.92 log i/o's/second
Checkpoint發生的時間、條件及髒頁的選擇等都很是複雜。而Checkpoint所作的事情無外乎是將緩衝池中的髒頁刷回到磁盤,不一樣之處在於每次刷新多少頁到磁盤,每次從哪裏取髒頁,以及什麼時間觸發Checkpoint。
在InnoDB存儲引擎內部,有兩種Checkpoint,分別爲:Sharp Checkpoint、Fuzzy Checkpoint
Sharp Checkpoint 發生在數據庫關閉時將全部的髒頁都刷新回磁盤,這是默認的工做方式,即參數innodb_fast_shutdown=1。可是若數據庫在運行時也使用Sharp Checkpoint,那麼數據庫的可用性就會受到很大的影響。故在InnoDB存儲引擎內部使用Fuzzy Checkpoint進行頁的刷新,即只刷新一部分髒頁,而不是刷新全部的髒頁回磁盤。
Fuzzy Checkpoint:一、Master Thread Checkpoint;二、FLUSH_LRU_LIST Checkpoint;三、Async/Sync Flush Checkpoint;四、Dirty Page too much Checkpoint
一、Master Thread Checkpoint
以每秒或每十秒的速度從緩衝池的髒頁列表中刷新必定比例的頁回磁盤,這個過程是異步的,此時InnoDB存儲引擎能夠進行其餘的操做,用戶查詢線程不會阻塞。
二、FLUSH_LRU_LIST Checkpoint
由於InnoDB存儲引擎須要保證LRU列表中須要有差很少100個空閒頁可供使用。在InnoDB1.1.x版本以前,須要檢查LRU列表中是否有足夠的可用空間操做發生在用戶查詢線程中,顯然這會阻塞用戶的查詢操做。假若沒有100個可用空閒頁,那麼InnoDB存儲引擎會將LRU列表尾端的頁移除。若是這些頁中有髒頁,那麼須要進行Checkpoint,而這些頁是來自LRU列表的,所以稱爲FLUSH_LRU_LIST Checkpoint。
而從MySQL 5.6版本,也就是InnoDB1.2.x版本開始,這個檢查被放在了一個單獨的Page Cleaner線程中進行,而且用戶能夠經過參數innodb_lru_scan_depth控制LRU列表中可用頁的數量,該值默認爲1024,如:
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_lru_scan_depth'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_lru_scan_depth | 1024 | +-----------------------+-------+
三、Async/Sync Flush Checkpoint
指的是重作日誌文件不可用的狀況,這時須要強制將一些頁刷新回磁盤,而此時髒頁是從髒頁列表中選取的。若將已經寫入到重作日誌的LSN記爲redo_lsn,將已經刷新回磁盤最新頁的LSN記爲checkpoint_lsn,則可定義:
checkpoint_age = redo_lsn - checkpoint_lsn
再定義如下的變量:
async_water_mark = 75% * total_redo_log_file_size
sync_water_mark = 90% * total_redo_log_file_size
若每一個重作日誌文件的大小爲1GB,而且定義了兩個重作日誌文件,則重作日誌文件的總大小爲2GB。那麼async_water_mark=1.5GB,sync_water_mark=1.8GB。則:
當checkpoint_age<async_water_mark時,不須要刷新任何髒頁到磁盤;
當async_water_mark<checkpoint_age<sync_water_mark時觸發Async Flush,從Flush列表中刷新足夠的髒頁回磁盤,使得刷新後知足checkpoint_age<async_water_mark;
checkpoint_age>sync_water_mark這種狀況通常不多發生,除非設置的重作日誌文件過小,而且在進行相似LOAD DATA的BULK INSERT操做。此時觸發Sync Flush操做,從Flush列表中刷新足夠的髒頁回磁盤,使得刷新後知足checkpoint_age<async_water_mark。
可見,Async/Sync Flush Checkpoint是爲了保證重作日誌的循環使用的可用性。在InnoDB 1.2.x版本以前,Async Flush Checkpoint會阻塞發現問題的用戶查詢線程,而Sync Flush Checkpoint會阻塞全部的用戶查詢線程,而且等待髒頁刷新完成。從InnoDB 1.2.x版本開始——也就是MySQL 5.6版本,這部分的刷新操做一樣放入到了單獨的Page Cleaner Thread中,故不會阻塞用戶查詢線程。
MySQL官方版本並不能查看刷新頁是從Flush列表中仍是從LRU列表中進行Checkpoint的,也不知道由於重作日誌而產生的Async/Sync Flush的次數。可是InnoSQL版本提供了方法,能夠經過命令SHOW ENGINE INNODB STATUS來觀察,如:
mysql> show engine innodb status \G BUFFER POOL AND MEMORY ---------------------- Total memory allocated 2058485760; in additional pool allocated 0 Dictionary memory allocated 913470 Buffer pool size 122879 Free buffers 79668 Database pages 41957 Old database pages 15468 Modified db pages 0 Pending reads 0 Pending writes: LRU 0, flush list 0, single page 0 Pages made young 15032929, not young 0 0.00 youngs/s, 0.00 non-youngs/s Pages read 15075936, created 366872, written 36656423 0.00 reads/s, 0.00 creates/s, 0.90 writes/s Buffer pool hit rate 1000 / 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: 41957, unzip_LRU len: 0 I/O sum[39]:cur[0], unzip sum[0]:cur[0]
四、Dirty Page too much
即髒頁的數量太多,致使InnoDB存儲引擎強制進行Checkpoint。其目的總的來講仍是爲了保證緩衝池中有足夠可用的頁。其可由參數innodb_max_dirty_pages_pct控制:
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_max_dirty_pages_pct' ; +----------------------------+-------+ | Variable_name | Value | +----------------------------+-------+ | innodb_max_dirty_pages_pct | 75 | +----------------------------+-------+
innodb_max_dirty_pages_pct值爲75表示,當緩衝池中髒頁的數量佔據75%時,強制進行Checkpoint,刷新一部分的髒頁到磁盤。在InnoDB 1.0.x版本以前,該參數默認值爲90,以後的版本都爲75。
在Innodb事務日誌中,採用了Fuzzy Checkpoint,Innodb每次取最老的modified page(last checkpoint)對應的LSN,再將此髒頁的LSN做爲Checkpoint點記錄到日誌文件,意思就是「此LSN以前的LSN對應的日誌和數據都已經flush到redo log
當mysql crash的時候,Innodb掃描redo log,從last checkpoint開始apply redo log到buffer pool,直到last checkpoint對應的LSN等於Log flushed up to對應的LSN,則恢復完成
那麼具體是怎麼恢復的呢?
如上圖所示,Innodb的一條事務日誌共經歷4個階段:
建立階段:事務建立一條日誌;
日誌刷盤:日誌寫入到磁盤上的日誌文件;
數據刷盤:日誌對應的髒頁數據寫入到磁盤上的數據文件;
寫CKP:日誌被看成Checkpoint寫入日誌文件;
對應這4個階段,系統記錄了4個日誌相關的信息,用於其它各類處理使用:
Log sequence number(LSN1):當前系統LSN最大值,新的事務日誌LSN將在此基礎上生成(LSN1+新日誌的大小);
Log flushed up to(LSN2):當前已經寫入日誌文件的LSN;
Oldest modified data log(LSN3):當前最舊的髒頁數據對應的LSN,寫Checkpoint的時候直接將此LSN寫入到日誌文件;
Last checkpoint at(LSN4):當前已經寫入Checkpoint的LSN;
對於系統來講,以上4個LSN是遞減的,即: LSN1>=LSN2>=LSN3>=LSN4.
具體的樣例以下(使用show innodb status \G命令查看,Oldest modified data log沒有顯示):
LOG --- Log sequence number 34822137537 Log flushed up to 34822137537 Last checkpoint at 34822133028 0 pending log writes, 0 pending chkp writes 54189288 log i/o's done, 3.00 log i/o's/second
mysql crash的時候,Innodb有日誌刷盤機制,能夠經過innodb_flush_log_at_trx_commit參數進行控制,這裏說的是如何防止日誌覆蓋致使日誌丟失
Innodb的checkpoint和redo log有哪些緊密關係?有几上名詞須要解釋一下:
Ckp age(動態移動): 最老的dirty page尚未flush到數據文件,即沒有作last checkpoint的範圍
Buf age(動態移動): modified page information沒有寫到log中,但已在log buffer
Buf async(固定點): 日誌空間大小的7/8,當buf age移動到Buf async點時,強制把沒有寫到log中的modified page information開始寫入到log中,不阻塞事務
Buf sync(固定點): 日誌空間大小的15/16,當寫入很大的,buf age移動很是快,一會兒到buf sync的點,阻塞事務,強制把modified page information開始寫入到log中。若是不阻塞事務,未作last checkpoint的redo log存在覆蓋危險
Ckp async(固定點): 日誌空間大小的31/32,當ckp age到達ckp async,強制作last checkpoint,不阻塞事務
Ckp sync(固定點):日誌空間大小,當ckp age到達ckp sync,強制作last checkpoint,阻塞事務,存在redo log覆蓋的危險
接下分析4種狀況
若是buf age在buf async和buf sync之間
若是buf age在buf sync以後(固然這種狀況是不存在,mysql有保護機制)
若是ckp age在ckp async和ckp sync之間(這種狀況是不存在)
若是ckp age在ckp sync以後(這種狀況是不存在)
第一種狀況:
當寫入量巨大時,buf age移動到buf async和buf sync之間,觸發寫出到log中,mysql把儘可能多的log寫出,若是寫入量減慢,buf age又移回到「圖一」狀態。若是寫入量大於flush log的速度,buf age最終會和buf sync重疊,這時全部的事務都被阻塞,強制將2*(Buf age-Buf async)的髒頁刷盤,這時IO會比較繁忙。
第二種狀況:
固然這種狀況是不可能出現,由於若是出現,redo log存在覆蓋的可能,數據就會丟失。buf age會越過log size,buf age的大小可能就超過log size,若是要刷buf age,那麼整個log size都不夠容納全部的buf age。
第三種和第四種狀況不存在分析:
ckp age始終位於buf age的後面(左邊),由於ckp age是last checkpoint點,老是追趕buf age(將盡量多的modified page flush到磁盤),因此buf age確定是先到達到buf sync。
ckp async及ckp sync存在乎義?
mysql中page cache也存在high water及low water,當dirty page觸到low water時,os是開始flush dirty page到磁盤,到high water時,會阻塞一切動做,os會瘋狂的flush dirty page,磁盤會很忙,存在IO Storm。