InnoDB 引擎核心知識點
6.1 邏輯存儲結構
- 表空間(Tablespace):所有數據邏輯上存儲在一個表空間中,物理上可能由多個文件組成。
- 段(Segment):分為數據段(B+樹葉子節點)、索引段(B+樹非葉子節點)、回滾段(Undo Log)。
- 區(Extent):由連續的頁組成,每個區固定為
1MB
(64個16KB的頁)。 - 頁(Page):InnoDB的最小存儲單元,默認
16KB
。 - 行(Row):數據按行存儲,支持行級鎖。
-- 示例:查看頁大小(默認16KB)
SHOW VARIABLES LIKE 'innodb_page_size';
6.2 架構
6.2.1 概述
MySQL5.5 版本開始,默認使用InnoDB存儲引擎,它擅長事務處理,具有崩潰恢復特性,在日常開發 中使用非常廣泛。下面是InnoDB架構圖,左側為內存結構,右側為磁盤結構。
在左側的內存結構中,主要分為這么四大塊兒: Buffer Pool、Change Buffer、Adaptive Hash Index、Log Buffer。 接下來介紹一下這四個部分。
InnoDB架構分為 內存結構 和 磁盤結構,通過后臺線程協調工作。
6.2.2 內存結構
-
Buffer Pool(緩沖池):核心組件,緩存數據和索引頁。
InnoDB存儲引擎基于磁盤文件存儲,訪問物理硬盤和在內存中進行訪問,速度相差很大,為了盡可能 彌補這兩者之間的I/O效率的差值,就需要把經常使用的數據加載到緩沖池中,避免每次訪問都進行磁 盤I/O。
在InnoDB的緩沖池中不僅緩存了索引頁和數據頁,還包含了undo頁、插入緩存、自適應哈希索引以及 InnoDB的鎖信息等等。
緩沖池 Buffer Pool,是主內存中的一個區域,里面可以緩存磁盤上經常操作的真實數據,在執行增 刪改查操作時,先操作緩沖池中的數據(若緩沖池沒有數據,則從磁盤加載并緩存),然后再以一定頻 率刷新到磁盤,從而減少磁盤IO,加快處理速度。
緩沖池以Page頁為單位,底層采用鏈表數據結構管理Page。根據狀態,將Page分為三種類型:
? free page:空閑page,未被使用。
? clean page:被使用page,數據沒有被修改過。
? dirty page:臟頁,被使用page,數據被修改過,也中數據與磁盤的數據產生了不一致。
在專用服務器上,通常將多達80%的物理內存分配給緩沖池 。參數設置: show variables like ‘innodb_buffer_pool_size’;
-- 示例:查看Buffer Pool大小 SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-
Change Buffer(寫緩沖):緩存非唯一索引的變更,減少磁盤IO。
Change Buffer,更改緩沖區(針對于非唯一二級索引頁),在執行DML語句時,如果這些數據Page 沒有在Buffer Pool中,不會直接操作磁盤,而會將數據變更存在更改緩沖區 Change Buffer 中,在未來數據被讀取時,再將數據合并恢復到Buffer Pool中,再將合并后的數據刷新到磁盤中。
Change Buffer的意義是什么呢?
先來看一幅圖,這個是二級索引的結構圖:
Change Buffer的意義是什么?
與聚集索引不同,二級索引通常是非唯一的,并且以相對隨機的順序插入二級索引。同樣,刪除和更新 可能會影響索引樹中不相鄰的二級索引頁,如果每一次都操作磁盤,會造成大量的磁盤IO。有了 ChangeBuffer之后,我們可以在緩沖池中進行合并處理,減少磁盤IO。
-
Adaptive Hash Index(自適應哈希索引):自動為頻繁訪問的數據創建哈希索引。
自適應hash索引,用于優化對Buffer Pool數據的查詢。MySQL的innoDB引擎中雖然沒有直接支持 hash索引,但是給我們提供了一個功能就是這個自適應hash索引。因為前面我們講到過,hash索引在 進行等值匹配時,一般性能是要高于B+樹的,因為hash索引一般只需要一次IO即可,而B+樹,可能需 要幾次匹配,所以hash索引的效率要高,但是hash索引又不適合做范圍查詢、模糊匹配等。
InnoDB存儲引擎會監控對表上各索引頁的查詢,如果觀察到在特定的條件下hash索引可以提升速度, 則建立hash索引,稱之為自適應hash索引。
自適應哈希索引,無需人工干預,是系統根據情況自動完成。
參數: adaptive_hash_index
-
Log Buffer(日志緩沖):存儲Redo Log的緩沖區,定期刷盤。(如果有一臺Mysql的服務器,那么一般80%的內存一般都會分配給緩存池,以此來提高Myql的執行效率。)
Log Buffer:日志緩沖區,用來保存要寫入到磁盤中的log日志數據(redo log 、undo log), 默認大小為 16MB,日志緩沖區的日志會定期刷新到磁盤中。如果需要更新、插入或刪除許多行的事 務,增加日志緩沖區的大小可以節省磁盤 I/O。
參數:
innodb_log_buffer_size:緩沖區大小
innodb_flush_log_at_trx_commit:日志刷新到磁盤時機,取值主要包含以下三個:
1: 日志在每次事務提交時寫入并刷新到磁盤,默認值。
0: 每秒將日志寫入并刷新到磁盤一次。
2: 日志在每次事務提交后寫入,并每秒刷新到磁盤一次。
6.2.3 磁盤結構
-
系統表空間(ibdata1):存儲元數據、Undo Log、Double Write Buffer。
系統表空間是更改緩沖區的存儲區域。如果表是在系統表空間而不是每個表文件或通用表空間中創建 的,它也可能包含表和索引數據。(在MySQL5.x版本中還包含InnoDB數據字典、undolog等)
參數:innodb_data_file_path
**系統表空間,默認的文件名叫 ibdata1。
-
用戶表空間(.ibd文件):每個表獨立存儲數據和索引。
File-Per-Table Tablespaces
如果開啟了innodb_file_per_table開關 ,則每個表的文件表空間包含單個InnoDB表的數據和索 引 ,并存儲在文件系統上的單個數據文件中。 開關參數:innodb_file_per_table ,該參數默認開啟。
那也就是說,我們每創建一個表,都會產生一個表空間文件,如圖:
-
通用表空間: General Tablespaces
通用表空間,需要通過 CREATE TABLESPACE 語法創建通用表空間,在創建表時,可以指定該表空 間。
A. 創建表空間
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;
B. 創建表時指定表空間
CREATE TABLE xxx ... TABLESPACE ts_name;
-
Undo Log(回滾日志):保證事務原子性,用于回滾和MVCC。
Undo Tablespaces
撤銷表空間,MySQL實例在初始化時會自動創建兩個默認的undo表空間(初始大小16M),用于存儲 undo log日志。
5). Temporary Tablespaces InnoDB 使用會話臨時表空間和全局臨時表空間。存儲用戶創建的臨時表等數據。
6). Doublewrite Buffer Files 雙寫緩沖區,innoDB引擎將數據頁從Buffer Pool刷新到磁盤前,先將數據頁寫入雙寫緩沖區文件 中,便于系統異常時恢復數據。
7). Redo Log 重做日志,是用來實現事務的持久性。該日志文件由兩部分組成:重做日志緩沖(redo log buffer)以及重做日志文件(redo log),前者是在內存中,后者在磁盤中。當事務提交之后會把所 有修改信息都會存到該日志中, 用于在刷新臟頁到磁盤時,發生錯誤時, 進行數據恢復使用。 以循環方式寫入重做日志文件,
涉及兩個文件:
前面我們介紹了InnoDB的內存結構,以及磁盤結構,那么內存中我們所更新的數據,又是如何到磁盤 中的呢? 此時,就涉及到一組后臺線程,接下來,就來介紹一些InnoDB中涉及到的后臺線程。
6.2.4 后臺線程
- Master Thread:負責異步刷盤、合并寫緩沖等核心任務。
- IO Thread:處理異步IO請求(如Redo Log寫入)。
- Purge Thread:清理Undo Log中無用的數據。
- Page Cleaner Thread:協助刷新臟頁。
6.3 事務原理
6.3.1 事務基礎
1). 事務 事務 是一組操作的集合,它是一個不可分割的工作單位,事務會把所有的操作作為一個整體一起向系 統提交或撤銷操作請求,即這些操作要么同時成功,要么同時失敗。 (例如:銀行轉賬)
2). 特性
? 原子性(Atomicity):事務是不可分割的最小操作單元,要么全部成功,要么全部失敗。
? 一致性(Consistency):事務完成時,必須使所有的數據都保持一致狀態。(數據+約束的一致性)
? 隔離性(Isolation):數據庫系統提供的隔離機制,保證事務在不受外部并發操作影響的獨立環境下運行。
? 持久性(Durability):事務一旦提交或回滾,它對數據庫中的數據的改變就是永久的。
那實際上,我們研究事務的原理,就是研究MySQL的InnoDB引擎是如何保證事務的這四大特性的。
而對于這四大特性,實際上分為兩個部分。 其中的原子性、一致性、持久化,實際上是由InnoDB中的 兩份日志來保證的,一份是redo log日志,一份是undo log日志。 而持久性是通過數據庫的鎖, 加上MVCC(多版本并發控制)來保證的。
我們在講解事務原理的時候,主要就是來研究一下redolog,undolog以及MVCC。
- ACID特性:原子性(Undo Log)、持久性(Redo Log)、隔離性(鎖+MVCC)、一致性(前三者共同保證)。
- 事務隔離級別:READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ(默認)→ SERIALIZABLE。
6.3.2 Redo Log
重做日志,記錄的是事務提交時數據頁的物理修改,是用來實現事務的持久性。
該日志文件由兩部分組成:重做日志緩沖(redo log buffer)以及重做日志文件(redo log file),前者是在內存中,后者在磁盤中。當事務提交之后會把所有修改信息都存到該日志文件中, 用于在刷新臟頁到磁盤,發生錯誤時, 進行數據恢復使用。
如果沒有redolog,可能會存在什么問題的? 我們一起來分析一下。
我們知道,在InnoDB引擎中的內存結構中,主要的內存區域就是緩沖池,在緩沖池中緩存了很多的數 據頁。 當我們在一個事務中,執行多個增刪改的操作時,InnoDB引擎會先操作緩沖池中的數據,如果 緩沖區沒有對應的數據,會通過后臺線程將磁盤中的數據加載出來,存放在緩沖區中,然后將緩沖池中 的數據修改,修改后的數據頁我們稱為臟頁。 而臟頁則會在一定的時機,通過后臺線程刷新到磁盤 中,從而保證緩沖區與磁盤的數據一致。 而緩沖區的臟頁數據并不是實時刷新的,而是一段時間之后 將緩沖區的數據刷新到磁盤中,假如刷新到磁盤的過程出錯了,而提示給用戶事務提交成功,而數據卻沒有持久化下來,這就出現問題了,沒有保證事務的持久性。
(buffer pool 存儲的數據。只是當前屏幕上顯示的數據, 修改它, 只是為了讓屏幕上顯示的數據進行變更。)
那么,如何解決上述的問題呢?(數據緩存池的臟數據丟失,導致應該持久化的數據無法持久化。) 在InnoDB中提供了一份日志 redo log,接下來我們再來分析一 下,通過redolog如何解決這個問題。
有了redolog之后,當對緩沖區的數據進行增刪改之后,會首先將操作的數據頁的變化,記錄在redo log buffer中。在事務提交時,會將redo log buffer中的數據刷新到redo log磁盤文件中(循環型,每隔一段時間就會清理,并不會永久保留下來,因為持久化完成之后這塊的數據就沒必要存在了。)。 過一段時間之后,如果刷新緩沖區的臟頁到磁盤時,發生錯誤,此時就可以借助于redo log進行數據 恢復,這樣就保證了事務的持久性。 而如果臟頁成功刷新到磁盤 或 或者涉及到的數據已經落盤,此 時redolog就沒有作用了,就可以刪除了,所以存在的兩個redolog文件是循環寫的。
那為什么每一次提交事務,要刷新redo log 到磁盤中呢,而不是直接將buffer pool中的臟頁刷新到磁盤呢 ? 因為在業務操作中,我們操作數據一般都是隨機讀寫磁盤的,而不是順序讀寫磁盤。 而redo log在 往磁盤文件中寫入數據,由于是日志文件,所以都是順序寫的。順序寫的效率,要遠大于隨機寫。 這 種先寫日志的方式,稱之為 WAL(Write-Ahead Logging:先寫日志)。
小結:
-
作用:發生臟頁數據丟失,也能保證事務持久性,記錄物理修改(如頁的變更)。
-
寫入流程:事務提交時,Redo Log先寫入日志文件,再異步刷盤。
事務提交 → Redo Log Buffer → 刷盤(fsync)→ 持久化
6.3.3 Undo Log(解決事務原子性)
回滾日志,用于記錄數據被修改前的信息 , 作用包含兩個 : 提供回滾(保證事務的原子性) 和 MVCC(多版本并發控制) 。
undo log和redo log記錄物理日志不一樣,它是邏輯日志。(物理日志:mysql數據最終是保存在數據頁中的,物理日志記錄的就是數據頁變更)可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的 update記錄。當執行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容并進行回滾。(key:反向的日志,目的是為了方便事務的回滾)
Undo log銷毀:undo log在事務執行時產生,事務提交時,并不會立即刪除undo log,因為這些 日志可能還用于MVCC。
Undo log存儲:undo log采用段的方式進行管理和記錄,存放在前面介紹的 rollback segment 回滾段中,內部包含1024個undo log segment。
- 作用:保證事務原子性,提供回滾功能,記錄邏輯反向操作(如INSERT對應DELETE)。
- 其他用途:支持MVCC,提供歷史版本數據。
6.4 MVCC(多版本并發控制)(高頻面試題)
6.4.1 基本概念
1). 當前讀(每一次讀操作都會加鎖)
讀取的是記錄的最新版本,讀取時還要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。對于我們日常的操作,如:select … lock in share mode(共享鎖),select … for update、update、insert、delete(排他鎖)都是一種當前讀。
- 通過 版本鏈 和 ReadView 實現非鎖定讀,解決讀寫沖突。
測試:
在測試中我們可以看到,即使是在默認的RR隔離級別下,事務A中依然可以讀取到事務B最新提交的內 容,因為在查詢語句后面加上了 lock in share mode 共享鎖,此時是當前讀操作。當然,當我們 加排他鎖的時候,也是當前讀操作。
2). 快照讀(每一次讀操作都不加鎖)
簡單的select(不加鎖)就是快照讀,快照讀,讀取的是記錄數據的可見版本,有可能是歷史數據, 不加鎖,是非阻塞讀。
? Read Committed:每次select,都生成一個快照讀。
? Repeatable Read:開啟事務后第一個select語句才是快照讀的地方。
? Serializable:快照讀會退化為當前讀。
測試:
在測試中,我們看到即使事務B提交了數據,事務A中也查詢不到。 原因就是因為普通的select是快照 讀,而在當前默認的RR隔離級別下,開啟事務后第一個select語句才是快照讀的地方,后面執行相同 的select語句都是從快照中獲取數據,可能不是當前的最新數據,這樣也就保證了可重復讀。
3). MVCC
全稱 Multi-Version Concurrency Control,多版本并發控制。**指維護一個數據的多個版本, 使得讀寫操作沒有沖突,快照讀為MySQL實現MVCC提供了一個非阻塞讀功能。**MVCC的具體實現,還需 要依賴于數據庫記錄中的三個隱式字段、undo log日志、readView。
接下來,我們再來介紹一下InnoDB引擎的表中涉及到的隱藏字段 、undolog 以及 readview,從 而來介紹一下MVCC的原理。
6.4.2 隱藏字段
介紹
當我們創建了上面的這張表,我們在查看表結構的時候,就可以顯式的看到這三個字段。 實際上除了 這三個字段以外,InnoDB還會自動的給我們添加三個隱藏字段及其含義分別是:
而上述的前兩個字段是肯定會添加的, 是否添加最后一個字段DB_ROW_ID,得看當前表有沒有主鍵, 如果有主鍵,則不會添加該隱藏字段。
文件名.ibd:獨立表空間文件。
DB_TRX_ID
:最近修改事務ID。DB_ROLL_PTR
:指向Undo Log中舊版本數據的(PTR:point是指針的簡寫)指針。
6.4.3 Undo Log版本鏈
6.4.3.1 介紹
回滾日志,在insert、update、delete的時候產生的便于數據回滾的日志。 當insert的時候,產生的undo log日志只在回滾時需要,在事務提交后,可被立即刪除。 而update、delete的時候,產生的undo log日志不僅在回滾時需要,在快照讀時也需要,不會立即 被刪除。
6.4.3.2 版本鏈
有一張表原始數據為:
DB_TRX_ID : 代表最近修改事務ID,記錄插入這條記錄或最后一次修改該記錄的事務ID,是 自增的。 DB_ROLL_PTR : 由于這條數據是才插入的,沒有被更新過,所以該字段值為null。
然后,有四個并發事務同時在訪問這張表。
A. 第一步
當事務2執行第一條修改語句時,會記錄undo log日志,記錄數據變更之前的樣子; 然后更新記錄, 并且記錄本次操作的事務ID,回滾指針,回滾指針用來指定如果發生回滾,回滾到哪一個版本。
B.第二步
當事務3執行第一條修改語句時,也會記錄undo log日志,記錄數據變更之前的樣子; 然后更新記 錄,并且記錄本次操作的事務ID,回滾指針,回滾指針用來指定如果發生回滾,回滾到哪一個版本。
C. 第三步
當事務4執行第一條修改語句時,也會記錄undo log日志,記錄數據變更之前的樣子; 然后更新記 錄,并且記錄本次操作的事務ID,回滾指針,回滾指針用來指定如果發生回滾,回滾到哪一個版本。
最終我們發現,不同事務或相同事務對同一條記錄進行修改,會導致該記錄的undolog生成一條 記錄版本鏈表,鏈表的頭部是最新的舊記錄,鏈表尾部是最早的舊記錄。
- 每個行記錄的多個版本通過
DB_ROLL_PTR
形成鏈表。
6.4.4 ReadView(版本鏈訪問規則不用記,了解)
ReadView(讀視圖)是 快照讀 SQL執行時MVCC提取數據的依據,記錄并維護系統當前活躍的事務 (未提交的)id。 ReadView中包含了四個核心字段:
而在readview中就規定了版本鏈數據的訪問規則:
trx_id 代表當前undolog版本鏈對應事務ID。
不同的隔離級別,生成ReadView的時機不同:
READ COMMITTED :在事務中每一次執行快照讀時生成ReadView。
REPEATABLE READ:僅在事務中第一次執行快照讀時生成ReadView,后續復用該ReadView。
- 關鍵字段:
m_ids
(活躍事務ID列表)、min_trx_id
(最小活躍事務ID)、max_trx_id
(預分配事務ID)、creator_trx_id
(當前事務ID)。 - 可見性規則:根據事務ID判斷數據版本是否可見。
6.4.5 原理分析
- 快照讀(如
SELECT
):基于ReadView讀取符合條件的歷史版本。 - 當前讀(如
SELECT FOR UPDATE
):讀取最新版本并加鎖。
重點總結
1.邏輯存儲結構
表空間、段、區、頁、行
2.架構
內存結構
磁盤結構
3.事務原理
原子性-undolog
持久性-redolog
一致性-undolog+redolog
隔離性-鎖+MVCC
4.MVCC
記錄隱藏字段、undolog版本鏈、readView