MySQL數據庫——InnoDB引擎(四種線程,緩存池)

目錄

 

1、4種後臺線程

1.1、Master Thread

1.2、IO Thread

1.3、Purge Thread

1.4、Page Cleaner Thread

2、InnoDB 引擎緩存

2.1、盤面,磁道,扇區,塊,頁的概念

2.2、InnoDB緩存的基本原理

2.3、緩存池空間管理——Free列表

2.4、頁的管理算法——LRU算法

2.5、髒頁的管理——Flush列表

2.6、重做日誌緩存 redo log(與 binlog 日誌的區別)

2.7、額外內存池

3、Checkpoint技術——刷髒頁技術


1、4種後臺線程

MySQL數據庫實例是一個進程,包含了多個線程,這篇文章將介紹一下InnoDB引擎的4種線程。

1.1、Master Thread

主線程。是數據庫功能的核心提供者,如果主線程崩潰了,那數據庫就沒法正常使用了,除非重啓主線程。

1.2、IO Thread

這是負責數據庫讀取和寫入的線程,默認讀取線程有4個,寫入線程有4個,當然可以通過設置下圖的兩個參數來增加或者減少線程數。

1.3、Purge Thread

在提交了一個事務給數據庫時,爲了保證事務能夠回滾,會在內存中緩存下該事務未執行時的狀態(undo 日誌),如果事務執行不成功,那麼就恢復之前的狀態,如果執行成功,undo日誌就用不着了,要把它刪除以騰出空間。Purge 線程就是做這個刪除工作的。

默認有4個線程。

1.4、Page Cleaner Thread

這個線程專門用於將內存中的“髒頁”刷入磁盤,使得磁盤內的數據和內存中的數據一致。內存就相當於數據庫的緩存,同一個數據存在兩個地方,就會出現數據不一致的情況。

默認是一個刷髒頁線程,查看 performance_schema 庫裏面的 global_variables 表格,裏面存的全局變量,包含了刷髒頁線程的線程數量。

髒頁線程的線程號是13,後臺運行,線程名爲 thread/innodb/page_cleaner_thread 。

MySQL 5.7 版本以後,支持設置多個刷髒頁線程,提高髒頁處理性能。設置命令,比如:SET GLOBAL innodb_page_cleaner = 3

2、InnoDB 引擎緩存

2.1、盤面,磁道,扇區,塊,頁的概念

先弄明白幾個概念:磁盤(盤面,磁道,扇區),內存(塊,頁)。

下面是磁盤的結構圖(網上搜的),左圖是一個盤面,右圖是磁盤結構,磁盤有很多個盤,每個盤有2面,2面都能存儲數據,在每一個面裏,有很多同心圓圈,這些圈就是一條條磁道,然後每條磁道又被分爲均勻地分爲很多扇區,扇區是磁盤讀取數據的最小單元(通常是512B),最小單元的意思就是不管你存1B 還是 512B,都會佔用一個扇區,沒有說我只用半個扇區,另外半個留給其它的數據用。

還有,爲什麼很多地方都講:對連續的物理存儲數據進行讀取的時候,速度也和內存隨機讀取差不多。因爲在同一條磁道上,如果數據內容是連續的,那直接旋轉讀取就OK,之所以說磁盤的IO速度慢,其中一部分原因就在於數據存儲的位置不連續的話,磁針就要多次去尋道(找磁道,並定位起始扇區),磁盤又是機械的,所以速度相對於內存來說,是慢很多的。

      

塊:操作系統是軟件層面的,它不認識什麼扇區,它將磁盤空間進行了抽象(換成自己認識的概念),塊就是操作系統對磁盤讀取數據的最小單元,一個塊通常對應於磁盤的一個扇區或者幾個連續的扇區(這就看操作系統的粗細粒度)。

頁:這個概念涉及操作系統尋址的知識了,64位的尋址總線,要能夠管理更大的內存,就需要使用分頁技術,每一頁代表一段連續的內存空間,一個地址對應一頁,先用地址去找到對應頁,至於裏面的內容,就用偏移量去找。所以頁是內存層面的概念。

到此,就比較明瞭了,比如,我們的軟件要讀取一頁的數據,會將指令交給操作系統,操作系統會按照塊爲單位進行讀取,底層的驅動則會驅動磁盤讀取出對應數量的扇區的數據。一頁對應若干塊,一塊通常對應若干扇區。

InnoDB引擎是軟件,用緩存技術在內存中緩存一小部分數據庫裏的數據,提高性能,所以InnoDB的腦子裏只有頁,並且它也是將數據庫按照頁的方式進行管理的,頁的大小默認是16k = 16384 bytes。 

2.2、InnoDB緩存的基本原理

由於InnoDB是基於磁盤的,但是磁盤的IO速度慢,爲了提升數據庫性能,InnoDB會在內存中緩存一部分數據,當用戶添加更新數據時,先看是否命中緩存,如果是,直接更新緩存中的數據,等到刷髒頁時機觸發時,再將緩存中的新數據寫入磁盤;當用戶讀取數據時,也是先看是否命中緩存,如果是,直接從緩存裏讀取數據即可。

InnoDB緩存在很多教材裏被叫作緩存池或者緩衝池,buffer_pool。在performance_schema庫的global_variables表裏,存放着全局變量,下圖是關於緩存池的配置參數。

innodb_buffer_poop_chunk_size:緩存池大小可以動態修改,但是要以innodb_buffer_poop_chunk_size爲單位,進行多少倍增加或者減少。

innodb_buffer_pool_instances:緩存池實例數量,默認爲1。建議設置爲CPU個數。

innodb_buffer_pool_size:所有緩存池的總大小,我這個版本的mysql默認爲 128M = 134217728 Bytes。如果要擴大,建議不要超過物理內存的80%,一般就設置在50% ~ 80%。

緩存池中存儲的數據類型:索引頁、數據頁、undo日誌頁、插入緩存、自適應哈希索引、鎖信息、數據字典信息等等,其中,索引頁和數據頁佔了大部分,InnoDB裏的索引是聚集索引,因此索引裏面既存了索引值,又存儲了記錄;而這裏的數據頁不是指用戶的數據,而是指 undo數據,系統數據,blob數據等等。

緩存池可以有多個,每個緩存池被稱爲緩存池實例,InnoDB可以有多個緩存池實例,每個頁根據哈希值取模來決定要存儲到那個緩存池實例(跟redis數據存儲於哪個槽有點像哦~~)。innodb_buffer_pool_instances 這個全局變量就是緩存池的數量,每個緩存池的大小 = innodb_buffer_pool_size / innodb_buffer_pool_instances。爲什麼要分爲多個緩存池呢?因爲要提升數據庫的併發性能,如果是一個緩存池的話,多個線程會競爭緩存池,影響性能,如果分爲多個緩存池實例的話,減小競爭,這也正好理解了“爲什麼建議緩存池實例數量設置爲CPU個數”。

每一個緩存池實例的狀態信息夠存儲在information_schema庫的 INNODB_BUFFER_POOL_STATS 裏,每個實例都有個Pool_id,用於區分緩存池實例。可以查看這個表。

一個緩存池實例的結構如下圖(借用的,好幾處文章都看見這個圖了,也不知道誰是原作者,勿噴),它不是單純地存儲每一個頁,試想,每一頁都存進去了,怎麼管理呢?哪一頁在哪個位置?這一頁數據代表什麼?因此,爲了方便管理,每一頁都有對應的一個控制塊,控制塊在緩存池的一端,緩存頁在另一端,塊裏的內容就是該頁所屬的表空間編號(space id)、頁號(page number)、頁在Buffer Pool中的地址,一些鎖信息以及LSN信息等等,每一個控制塊的大小都是相同的,每一頁的大小也是相同的,所以我們要好好計算 innodb_buffer_pool_instances 和 innodb_buffer_pool_size,以避免出現下圖的內存碎片。

緩存池看起來有點像一個列表。

2.3、緩存池空間管理——Free列表

當需要緩存一頁數據時,我們怎麼指定緩存池中哪些頁(緩存池的空間是按頁劃分管理的)是空白的,不然萬一存在某個有數據的頁空間去,覆蓋掉原先的數據就慘了。

所以,InnoDB用了一個Free列表去存儲緩存池中所有空白的頁空間情況, 比如每一頁的起始地址等等。每次需要在緩存池存儲頁的時候,都會看看Free列表是否有空閒的頁,如果沒有,那就只有清空一個頁,騰出一個位置給新頁。

一個緩存池實例對應一個Free表。

2.4、頁的管理算法——LRU算法

一個緩存池實例就那麼大點,存儲的頁有限,因此,有一個算法LRU(最近最少使用算法)被用來管理緩存池,決定哪些頁被清出緩存池,哪些頁被加入緩存池。InnoDB用了一個LRU列表來存儲緩存頁標識,列表裏對應的緩存頁代表目前還在緩存池中,越是靠近列表頭,說明該頁越是最近被頻繁使用,越在列表尾部,說明越是不怎麼被使用。並且,還定義了一個midpoint位置,這個位置之前代表new頁(頻繁被用),這個位置之後(包括midpoint本身)代表old頁(不怎麼被用),old頁容易被清出緩存池。

這個midpoint位置默認處於列表尾部的37%處,全局變量 innodb_old_blocks_pct 就代表這個位置。每當有一個頁需要存儲到緩存池時,不是放在列表頭,而是放在midpoint位置,如果此時緩存池已滿,就將列表末端頁刪除騰出一個位置給新頁,如果此時緩存還沒有滿,那就直接放在midpoint位置。

old頁就永遠是old頁嗎?不是,它轉爲new頁的時機需要根據innodb_old_blocking_time(單位毫秒),默認是1秒,當用戶讀取數據庫數據時,假如就讀取一頁,這一頁數據被緩存在緩存池中midpoint位置,從此刻開始的innodb_old_blocking_time時間內,不管用戶讀取它多少次,都不會變爲new頁放到列表前面去,只有當innodb_old_blocking_time之後,一旦用戶再次讀取它,它就會變爲new頁了。

我們可以調整下面2個參數來減小熱點數據被清出的概率。

另外,Free表大小 + LRU表大小 = 緩存池頁數量。  一個緩存池實例對應一個LRU表,在information_schema庫裏的INNODB_BUFFER_PAGE_LRU表裏可以查看LRU表格情況。

2.5、髒頁的管理——Flush列表

如果用戶要修改某數據,先是看數據是否命中緩存池,如果命中,會將新值更新到緩存池,此時,緩存池裏的數據和磁盤裏的數據不一致了,就需要刷髒頁,將緩存池裏的新數據寫入磁盤,使其一致。但是我們知道磁盤IO速度慢,如果用戶頻繁地更改數據,那豈不是影響性能。

因此InnoDB用了一個Flush列表存儲髒頁信息,每隔一段時間,或者時機到了,就會觸發一次刷髒頁,將Flush表的髒頁一起處理,Flush表的信息在information_schema庫的INNODB_BUFFER_PAGE_LRU裏,和LRU表信息放在一起的,只是Flush表的信息需要增加查詢條件:OLDEST_MODIFICATION > 0。

在刷髒頁的時候,是有專門的線程去執行的,我們考慮一下按什麼樣的順序去刷髒頁,我個人想法有2種:1、某一頁被修改的次數,次數越多,說明與磁盤的數據越不一致,越是急迫需要刷新(第一種好像是InnoDB的刷新策略);2、某一頁第一次被修改的時間,越早被修改過,說明磁盤裏的數據越不實時,越應該被優先刷新。

2.6、重做日誌緩存 redo log(與 binlog 日誌的區別)

先講講一個事務的執行過程吧,首先,事務開始,對要修改的數據上鎖,如果數據沒有在緩存池內,就需要將數據從磁盤裏讀到緩存池內,如果數據已經在緩存池就更好了,然後將事務對頁的修改進行redo日誌記錄,存放在redo日誌緩存中,緩存好了之後,纔開始修改緩存池中的數據,修改完畢後,事務被提交,表明事務執行完了,事務的提交會觸發將redo日誌緩存刷新到磁盤中的redo日誌文件中。

redo日誌緩存的作用:當用戶將要執行一個事務時,會在事務執行之前,將事務對頁的修改存入redo日誌緩存中,等事務修改了數據後,並提交事務,然後才觸發將redo日誌緩存裏的數據寫入磁盤中的redo日誌文件中,以保存起來。

爲什麼要redo日誌呢?因爲事務執行完畢並提交了事務後,修改的數據還在緩存池裏,磁盤還沒有刷新,如果此時系統崩潰了或者斷電了,緩存池裏的數據都沒了,該事務就等於沒有執行。如果有了redo日誌文件(磁盤裏的),我們就可以恢復事務執行的結果了。因此,一旦緩存池的頁被刷新到磁盤後,其對應的redo日誌數據就沒用了,因爲最新的數據已經持久化到磁盤了,不會丟失了,自然就用不着redo日誌了,這種redo日誌被叫作無用redo日誌。

那redo日誌緩存區到底有多大呢?我這個版本的Mysql給的默認值是16K = 16777216 bytes。其它資料裏說,8M基本上就夠存儲大部分應用的事務redo日誌了。

redo日誌緩存刷入redo日誌文件的時機:1、定期(master線程做); 2、每個事務提交時,就會觸發redo日誌緩存刷入,及時將該事務的redo日誌存入磁盤;3、當redo日誌緩存空間剩餘不到1/2時,也會觸發。

關於第二個時機,再具體講一點,redo 緩存內容會先寫入系統文件緩存中,在fsysn操作後,纔會同步到磁盤中,下面有個全局參數,默認爲1,表示每個事務提交時,都會觸發執行一個 fsysn 操作,即將 redo 日誌刷入磁盤,但是我們還可以將參數設置爲 0, 表示提交事務不觸發redo 日誌刷新, 設置爲 2, 表示事務提交時,僅會把 redo 日誌緩存寫入 系統文件緩存,不會執行 fsysn 操作,我想什麼時候 fsysn 操作會被執行呢? 可能這就是操作系統的策略了吧,如果數據庫崩了,那系統文件緩存裏還有redo數據,如果操作系統崩了或者重啓,那就沒了。

另外,還有一個binlog日誌,這個日誌是應用層面的,和數據庫引擎沒有關係,binlog 日誌的作用目的和redo log差不多,但是也有區別,binlog 用於存儲對數據庫的所有改動sql,存的sql級別的,而且是所有的,而 redo 日誌記錄的是物理內存級別上的修改,更加底層。

爲什麼要有2種呢,因爲 redo 日誌記錄的是底層級別的修改記錄,一旦用redo 日誌恢復起來,redo日誌裏的修改操作被執行起來的併發性更好,而 binlog 是應用層面的修改操作,執行起來效率更低。

2.7、額外內存池

緩存池用於存儲頁,對於每一頁,還需要存儲它的信息,比如這一頁對應磁盤裏的是那一部分的空間,這一頁在緩存池中的地址等等,另外還有LRU表,Free表,Flush表,這些內容都存放在哪裏呢?不是存在緩存池,而是存在額外內存池中。

因此,不要忽略額外內存池,因爲如果緩存池越大,額外內存池需要記錄的數據也越多,應該相應的擴大額外內存池的空間。

在MySQL 5.7.4 版本之前,全局參數 innodb_additional_mem_pool_size 表示額外內存池大小, innodb_use_sys_malloc 表示是否讓操作系統替它分配內存,ON表示要讓,OFF表示不讓,InnoDB自己分配。

在MySQL 5.7.4 版本之後, innodb_additional_mem_pool_size 和 innodb_use_sys_malloc 被取消了,直接交由操作系統來分配額外內存池,你要多大,操作系統就給你分配多大,不用我們去關心了。

3、Checkpoint技術——刷髒頁技術

上面已經簡單的介紹了刷髒頁,這一節詳細地說一說。

1、緩存某個頁的時候,如果某個緩衝池已經滿了,此時會根據LRU算法將LRU列表尾的頁清除掉,如果該頁是髒頁,在清除之前,要進行一次刷髒頁操作。  (換一種專業說法,首先查看Free列表是否有空閒頁,如果沒有,那就根據LRU算法將LRU列表末尾的頁刪除以騰出空間,在刪除之前,看看該末尾頁是否在Flush列表中,如果是,說明該末尾頁是在髒頁,觸發一次刷髒頁,刷新後,再刪除該末尾頁騰出空間)。

2、磁盤裏的redo日誌文件大小有限,因此redo日誌數據會循環地進行覆蓋存儲,但是隻能覆蓋那些無用redo日誌,如果此時沒有無用redo日誌給新的日誌覆蓋,怎麼辦呢?會立即觸發一次刷髒頁,部分髒頁被刷後,這些髒頁對應的redo日誌就沒用了,那新日誌就可以覆蓋了。

刷髒頁並不是每次都把所有的髒頁刷新,而是刷新一部分髒頁,保證數據庫的性能,如果每次都全部刷新的話,豈不是用戶要等數據庫刷完了才能進行訪問和操作。

InnoDB 有兩種刷髒頁方式(不是二選一,而是並存的):Sharp Checkpoint 和 Fuzzy CheckPoint。

Sharp Checkpoint 是在數據庫關閉的時候執行,默認把所有的髒頁刷新。 全局參數 innodb_fast_shutdown 代表了 Sharp Checkpoint執行的方式,有3個值(0,1,2)。0表示在innodb關閉的時候,需要purge all, merge insert buffer,flush dirty pages。這是最慢的一種關閉方式,但是restart的時候也是最快的。1表示在innodb關閉的時候,它不需要purge all,merge insert buffer,只需要flush dirty page。 2表示在innodb關閉的時候,它不需要purge all,merge insert buffer,也不進行flush dirty page,只將log buffer裏面的日誌flush到log files。因此等下進行恢復的時候它是最耗時的。

Fuzzy CheckPoint 是數據庫運行期間刷髒頁機制,前文基本上講得差不多了,總結爲:1、master thread 定期刷髒頁; 2、緩存池中的髒頁刷新騰出空間; 3、redo日誌文件無法繼續覆蓋地進行存儲,而刷髒頁。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章