MySQL 數(shù)據(jù)庫InnoDB 存儲引擎的底層邏輯架構(gòu)
MySQL 數(shù)據(jù)庫有很多個存儲引擎,其中另我們印象深刻的應(yīng)該是 InnoDB 存儲引擎,它從 MySQL 5.5 之后就是默認的存儲引擎,它有支持事務(wù)、行級鎖、MVCC 以及外鍵等優(yōu)點。
那么你知道InnoDB
存儲引擎的底層邏輯架構(gòu)嗎?下面我們就來聊一下InnoDB
存儲引擎。
InnoDB
存儲引擎主要由兩個部分組成,分別是內(nèi)存架構(gòu)和磁盤架構(gòu),這兩個部分都有自己不可或缺的功能。下面我們就通過一張圖來詳細了解一下這兩個部分。
內(nèi)存架構(gòu)
內(nèi)存架構(gòu)(英文名稱:In-Memory Structures
),在InnoDB
存儲引擎中主要包括四個部分,分別是自適應(yīng)哈希索引、Buffer pool
、Change buffer
和Log Buffer
四個部分。
1. 自適應(yīng)哈希索引
首先我們來聊聊自適應(yīng)哈希索引,自適應(yīng)哈希索引的英文名稱:Adaptive Hash Index
。它的設(shè)計目的是想讓 MySQL 數(shù)據(jù)庫像內(nèi)存數(shù)據(jù)庫一樣高效,同時不會丟掉事務(wù)、行鎖以及外鍵等特性。
它并不是我們?nèi)藶槿?chuàng)建的,而是InnoDB
存儲引擎通過索引監(jiān)控機制去自動創(chuàng)建的,也就是說如果InnoDB
存儲引擎監(jiān)控到自適應(yīng)哈希索引可以提高查詢速度,隨即InnoDB
存儲引擎會自動為本次查詢創(chuàng)建自適應(yīng)哈希索引。命中了自適應(yīng)哈希索引的查詢就不會觸發(fā)全表掃描,而是直接通過索引拿需要的數(shù)據(jù),這樣就可以提高數(shù)據(jù)庫的查詢速度。
但是自適應(yīng)哈希索引并不是任何情況下都可以使用,例如:link '%xxx'
,這是因為 link 前置百分號查詢本身就需要全表掃描,所以用與不用索引的結(jié)果都是一樣的,用索引反而會多此一舉,因此這種情況下不需要創(chuàng)建自適應(yīng)哈希索引。
2. Buffer pool
Buffer pool(中文名稱:緩沖池),是 MySQL 數(shù)據(jù)庫中最重要的一個部分。在數(shù)據(jù)庫啟動之時,首先會初始化這塊內(nèi)存區(qū)域,它占用了 MySQL 數(shù)據(jù)庫總內(nèi)存空間的80%
以上。詳細情況可以通過show engine innodb statusG
來查看:
mysql> show engine innodb statusG ---------------------- BUFFER POOL AND MEMORY ---------------------- Total large memory allocated 137428992 Dictionary memory allocated 301572 Buffer pool size 8191 Free buffers 6916 Database pages 1252 Old database pages 442 Modified db pages 0 Pending reads 0 Pending writes: LRU 0, flush list 0, single page 0 Pages made young 258, not young 1 0.00 youngs/s, 0.00 non-youngs/s Pages read 320, created 938, written 3279 0.00 reads/s, 0.00 creates/s, 0.00 writes/s No buffer pool page gets since the last printout Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s LRU len: 1252, unzip_LRU len: 0 I/O sum[0]:cur[0], unzip sum[0]:cur[0]復(fù)制代碼
它的主要作用是提高數(shù)據(jù)庫查詢的效率,其中主要使用了LRU
算法,下面我們一起來詳細了解一下LRU
算法。
在MySQL
數(shù)據(jù)庫中,LRU
算法的底層主要是一個鏈表。不過該鏈表被分為了兩個區(qū)域,分別是新子鏈表和舊子鏈表,而且新子鏈表占用總空間的5/8
,舊子鏈表占用總空間的3/8
。其主要實現(xiàn)的步驟如下:
- 第一步:假設(shè)我們讀取
數(shù)據(jù)2
,這個時候恰好數(shù)據(jù)2
在新子鏈表中,這個時候,會將數(shù)據(jù)2
調(diào)換至新子鏈表的開頭。 - 第二步:如果查詢一條
buffer pool
中沒有的數(shù)據(jù)時,MySQL
數(shù)據(jù)庫將在磁盤中讀取出該條數(shù)據(jù)數(shù)據(jù)X
,并且插入新子鏈表后面,同時會淘汰舊子鏈表中的數(shù)據(jù)N
。
說到這里,可能就會有朋友問了,既然新數(shù)據(jù)移到鏈表的最前方,排列在最后面的數(shù)據(jù)直接淘汰,那么為什么還需要一個新子鏈表和舊子鏈表呢?
這個時候我們設(shè)想一下,假設(shè)我們查詢一個比較大的數(shù)據(jù),可能會占滿所有的Buffer pool
內(nèi)存空間,按照我們理解的淘汰策略,這個時候會一下子將所有的數(shù)據(jù)全部淘汰。而這個時候正在高速運轉(zhuǎn)的數(shù)據(jù)庫會將所有的查詢?nèi)孔饔糜诖疟P,那將會導(dǎo)致系統(tǒng)磁盤 IO 急劇升高且數(shù)據(jù)庫反應(yīng)緩慢,最終會導(dǎo)致用戶體驗下降。
這個時候我們再看,如果把所有新查詢的數(shù)據(jù)全部存放于新子鏈表中,查詢的數(shù)據(jù)最多把新子鏈表中的空間全部占滿而舊子鏈表中仍然保留著之前的數(shù)據(jù),對于高速運轉(zhuǎn)的數(shù)據(jù)庫來講,就不會導(dǎo)致系統(tǒng)磁盤 IO 急劇升高和數(shù)據(jù)庫反應(yīng)緩慢了。這也正是新舊子鏈表設(shè)計的初衷。
在Buffer Pool
存儲塊中還保留有一個小內(nèi)存塊,即Change buffer
。下面我們就來聊聊這塊內(nèi)存是用來做什么的。
3. Change buffer
Change Buffer
的另一個名字叫“寫緩存”。見名知意,Change Buffer
主要的功能是記錄數(shù)據(jù)庫的數(shù)據(jù)修改操作的結(jié)果的。主要目的是提高數(shù)據(jù)庫的寫性能。
下面我們就來詳細分析一下,數(shù)據(jù)修改操作的步驟。
- 第一步:修改一條數(shù)據(jù)時,首先判斷該條數(shù)據(jù)是否存在于
Buffer Pool
之中。 - 如果在,直接修改
Buffer Pool
中的相關(guān)數(shù)據(jù)。 - 如果不在,首先在磁盤中讀取該條數(shù)據(jù)到
Change Buffer
之中,而后在Change Buffer
中修改該數(shù)據(jù),同時寫入Redo Log
之中(為了防止數(shù)據(jù)丟失),等下一次查詢該條數(shù)據(jù)時,合并至Buffer Pool
中。 - 第二步:
Change Buffer
中數(shù)據(jù)修改之后,什么時候合并數(shù)據(jù)呢? - 第一種方式:當(dāng)修改的這條數(shù)據(jù)被查詢的時候,合并到
Buffer Pool
。 - 第二種方式:MySQL 數(shù)據(jù)庫中的
Master Thread
合并(周期默認:10s)。 - 第三種方式:當(dāng) MySQL 數(shù)據(jù)庫關(guān)閉時,通過
Redo Log
合并到磁盤中。
Change Buffer
之所以這樣設(shè)計,是因為對于高速運轉(zhuǎn)的 MySQL 數(shù)據(jù)庫來講,如果每一次修改都修改磁盤同時又修改Buffer Pool
中的內(nèi)容的話,對于 MySQL 數(shù)據(jù)庫來講代價太大了,磁盤的 IO 也會非常高,最終會導(dǎo)致 MySQL 數(shù)據(jù)庫運行緩慢。那么,修改數(shù)據(jù)時使用Change Buffer
就相當(dāng)于在內(nèi)存中修改數(shù)據(jù),并且保存在內(nèi)存中,當(dāng)數(shù)據(jù)庫空閑時才會寫入磁盤,這樣既能夠達到修改數(shù)據(jù)的目的,又能夠降低數(shù)據(jù)庫對于系統(tǒng)的性能要求,進而提高數(shù)據(jù)庫的性能。
上面我們提到,Change Buffer
修改完成之后,會修改redo log
中的數(shù)據(jù),那么接下來我們就來了解一下Log Buffer
。
4. Log Buffer
我們設(shè)想一下,如果在Change Buffer
修改完數(shù)據(jù)之后,僅僅保存在內(nèi)存中,那么如果這個時候數(shù)據(jù)庫宕機,也就意味著我們剛剛修改的數(shù)據(jù)也隨即丟失,而這一點是不能被允許的。
怎么解決這個問題呢?MySQL 給我們提供了一種寫日志的方案,也就是說,修改完的數(shù)據(jù)會保存到一個叫Redo Log
(具體請參考下方的Redo Log
部分)的日志中。它是一個物理日志,當(dāng)數(shù)據(jù)宕機時,它會將數(shù)據(jù)直接保存在磁盤之上;當(dāng)數(shù)據(jù)庫開啟時,自動寫入到數(shù)據(jù)庫的磁盤中,以至于數(shù)據(jù)不會丟失。
上方我們提到了,Redo Log
是一個物理日志,如果把大量的數(shù)據(jù)直接寫進磁盤,還是會導(dǎo)致數(shù)據(jù)庫性能低下,我們用一個Log Buffer
來保存需要寫入Redo log
的數(shù)據(jù),這樣有利于提高數(shù)據(jù)庫的性能。
這個時候你可能會問:那Change Buffer
為什么不直接寫入磁盤呢?
具體情況是這樣的,MySQL 數(shù)據(jù)庫在系統(tǒng)磁盤上保存的數(shù)據(jù)是有序的(典型就是按照主鍵 ID),如果每一次修改數(shù)據(jù)直接操作磁盤的話,會導(dǎo)致很多數(shù)據(jù)的位置發(fā)生更改(也就是我們常說的:隨機 IO),但是Redo log
中保存的數(shù)據(jù)是無序的,隨意不會產(chǎn)生隨機 IO,所以使用Redo log
暫時保存數(shù)據(jù)是確保數(shù)據(jù)不丟失時的最好辦法。
聊完InnoDB
存儲引擎的內(nèi)存架構(gòu)之后,接下來我們再來了解一下InnoDB
存儲引擎的磁盤架構(gòu)。
磁盤架構(gòu)
對于InnoDB
存儲引擎來說,磁盤架構(gòu)最重要的就是表空間了。InnoDB
存儲引擎的表空間主要分為:系統(tǒng)表空間、獨立表空間、普通表空間、Undo表空間以及臨時表空間。
下面我們一起來詳細聊聊InnoDB
存儲引擎的磁盤架構(gòu)中的各個表空間。
1. 系統(tǒng)表空間
系統(tǒng)表空間是InnoDB
存儲引擎中最重要的表空間之一,它的主要作用是存儲InnoDB
數(shù)據(jù)字典、雙寫緩沖、更改緩存以及撤銷日志。
系統(tǒng)表空間一般存放于 MySQL 數(shù)據(jù)庫目錄中,名稱為:ibdata1
。系統(tǒng)表空間一般不一定只有一個,也可能有多個,系統(tǒng)表空間的大小和數(shù)量由innodb_data_file_path
控制。具體如下:
mysql> SHOW VARIABLES LIKE 'innodb_data_file_path';+-----------------------+------------------------+| Variable_name | Value |+-----------------------+------------------------+| innodb_data_file_path | ibdata1:12M:autoextend |+-----------------------+------------------------+1 row in set (0.00 sec)復(fù)制代碼
在這里特別需要說明的是,InnoDB 數(shù)據(jù)字典在 MySQL 8.0 版本以后合并至 MySQL 數(shù)據(jù)字典中了,不再存儲在系統(tǒng)表空間中了。
這個時候你可能會問,MySQL 數(shù)據(jù)表中的數(shù)據(jù)存放于哪里呢?下面我們就來聊一聊這個問題。
2. 獨立表空間
對于innodb
存儲引擎來說,我們通常創(chuàng)建數(shù)據(jù)表的時候,會在 MySQL 數(shù)據(jù)目錄中創(chuàng)建兩個文件,分別是.ibd
和.frm
兩個文件。.ibd
文件主要用來存儲表數(shù)據(jù),而.frm
文件主要用來存儲索引。
這種做法可以將所有的數(shù)據(jù)表分開管理,也能夠?qū)崿F(xiàn)快速數(shù)據(jù)遷移,當(dāng)數(shù)據(jù)出現(xiàn)故障之時也可以提高數(shù)據(jù)恢復(fù)的成功率。不過這樣的做法又會增加磁盤的碎片,對系統(tǒng)處理表文件的性能有一定的影響。
3. 普通表空間
普通表空間的本質(zhì)其實就是一個共享的表空間。其具體文件在 MySQL 數(shù)據(jù)庫的數(shù)據(jù)目錄中是以.ibd
結(jié)尾的文件。跟系統(tǒng)表空間類似,它支持所有 MySQL 數(shù)據(jù)庫中的數(shù)據(jù)表的結(jié)構(gòu),它是將數(shù)據(jù)庫的一些元數(shù)據(jù)保存在內(nèi)存之中,進而能夠減少獨立表空間對于內(nèi)存的消耗。
4. Undo 表空間
Undo 表空間主要是用來保存撤銷日志(即:Undo Log
)的空間。它默認情況下存儲在 MySQL 數(shù)據(jù)庫的根目錄。我們可以通過以下方式來查看:
mysql> SHOW VARIABLES LIKE 'innodb_undo_directory';+--------------------------+-------+| Variable_name | Value |+--------------------------+-------+| innodb_undo_directory | ./ |+--------------------------+-------+4 rows in set (0.00 sec)復(fù)制代碼
在MySQL 8.0
版本之后,undo 表空間會在 MySQL 數(shù)據(jù)庫的數(shù)據(jù)根目錄生成 undo_001
和 undo002
共兩個文件。
5. 臨時表空間
臨時表空間主要是用來保存數(shù)據(jù)庫會話中的臨時數(shù)據(jù)的。在 MySQL 數(shù)據(jù)庫的數(shù)據(jù)根目錄中保存以ibtmp1
命名的文件。最主要的是我們在使用 join 連表查詢的時候,會在臨時表空間內(nèi)創(chuàng)建臨時數(shù)據(jù)表用來輔助查詢。我們可以通過以下方式來查看臨時表空間的配置:
mysql> SELECT @@innodb_temp_data_file_path;+------------------------------+| @@innodb_temp_data_file_path |+------------------------------+| ibtmp1:12M:autoextend |+------------------------------+1 row in set (0.00 sec)復(fù)制代碼
總結(jié)
InnoDB
存儲引擎是 MySQL 數(shù)據(jù)庫中最重要的一個存儲引擎之一。今天我們一起通過它的內(nèi)存架構(gòu)和磁盤架構(gòu)深入地了解了它的底層架構(gòu)。
在內(nèi)存架構(gòu)中,自適應(yīng)哈希索引有利于提高查詢速度;Buffer pool
主要提供了一個內(nèi)存池,將經(jīng)常查詢的數(shù)據(jù)存放于內(nèi)存中,這樣做有利于提高數(shù)據(jù)庫的查詢性能和降低系統(tǒng)的磁盤 IO;Change buffer
主要是將修改好的數(shù)據(jù)存放于內(nèi)存之中,下一次查詢的時候合并到Buffer pool
之中,這樣做的好處是可以降低修改數(shù)據(jù)時的磁盤 IO,進而提高數(shù)據(jù)庫的性能;Log Buffer
是將所有修改的數(shù)據(jù)存放在其中,之后寫入到Redo Log
之中,防止數(shù)據(jù)丟失。
在磁盤架構(gòu)中,系統(tǒng)表空間是用來修改和撤銷日志的地方,之前的數(shù)據(jù)庫版本中還存放InnoDB
數(shù)據(jù)字典以及雙寫緩沖;獨立表空間主要是用來存儲表數(shù)據(jù)和索引的地方;普通表空間是一個共享的表空間,能夠減少獨立表空間對于內(nèi)存的消耗;Undo 表空間主要作用于事務(wù)回滾的,在使用未提交之前,用來保存原來的數(shù)據(jù),一旦事務(wù)回滾則用 Undo 表空間中的內(nèi)容替換修改過后的數(shù)據(jù),進而達到回滾的目的;臨時表空間主要是一個過渡的表空間,通常的一些操作需要有這種過渡來輔助操作,例如連表查詢。
從內(nèi)存架構(gòu)到磁盤架構(gòu),InnoDB
存儲引擎為我們提供了一個高性能、高安全的數(shù)據(jù)庫存儲引擎。通常在實際應(yīng)用過程中,InnoDB
存儲引擎是我們的首選存儲引擎,但是在使用過程中一定要把Buffer pool
的空間設(shè)置得足夠大,這樣有利于提高數(shù)據(jù)的查詢性能。
作者:NPy
來源:稀土掘金
版權(quán)聲明:
本站所有文章和圖片均來自用戶分享和網(wǎng)絡(luò)收集,文章和圖片版權(quán)歸原作者及原出處所有,僅供學(xué)習(xí)與參考,請勿用于商業(yè)用途,如果損害了您的權(quán)利,請聯(lián)系網(wǎng)站客服處理。