Innodb
1. 架構
1.1 內存部分
buffer pool
緩沖池是主存中的第一個區域,里面可以緩存磁盤上經常操作的真實數據,在執行增刪查改操作時,先操作緩沖池中的數據,然后以一定頻率刷新到磁盤,這樣操作明顯提升了速度。
緩沖池以page為單位,底層采取鏈表的數據結構管理page,這一點和xv6是一模一樣的,根據狀態,分為三類:
- 空閑頁free page
- 干凈頁clean page,分配之后沒有被修改過
- 臟頁dirty page,數據被修改過,與磁盤中的數據不一致
change buffer
更改緩沖區,字面意思,主要針對于二級索引頁,當我們在buffer pool執行數據變更操作(DML)的時候,如果沒有找到這個數據,并不會直接讀取磁盤數據,而是將其寫入到change buffer,等未來讀取數據的時候,直接將數據合并,恢復到buffer pool中,再將數據刷新到磁盤中,由于二級索引頁插入刪除相對隨機,所以如果每次寫入都會進行磁盤IO的話,性能太差,而使用change buffer,就減少了磁盤IO,提高了讀寫效率。
自適應哈希
自適應哈希索引,我們的InnoDB引擎默認是B+樹索引,相對于哈希表慢一點,但是哈希表不支持范圍查詢,所以InnoDB為了提高查詢效率,為buffer pool添加了一個自適應哈希索引,我們的InnoDB引擎會自己監控各個索引頁上的索引,如果觀察到hash索引可以提升查詢速度,就會建立哈希索引,這個過程無需人工干預,但是我們需要知道這個參數是adaptive_hash_index
,默認開啟。
log buffer
保存要寫入到磁盤中的兩份日志:redo log和undo log,默認大小16M,會定期刷新到磁盤,如果更新插入較頻繁,增加緩沖區大小可以減少磁盤IO,參數:
- innodb_log_buffer:緩沖區大小。
- innodb_flush_log_trx_commit:刷新到磁盤的時機。
- 1:每次事務提交寫入
- 0:每秒寫入
- 2:兩者結合
1.2 磁盤部分
系統表空間
主要處理非主鍵非唯一的二級索引的更改緩沖區,也就是change buffer,當然,這個和內存中的change buffer不一樣,它主要用來存儲我們的在真正寫入表空間的變更操作,為啥不直接寫入表空間?,原因是這樣可以進一步避免頻繁的磁盤IO,我們緩存的是變更操作,而不是整頁的數據,并且在磁盤上,可以緩存大量變更。
文件表空間
我們建立的每一張表都有一個獨立的表空間,文件表空間都包含單個InnoDB表的結構,數據和索引,并且存儲在文件系統的單個數據文件中,參數:innodb_file_per_table
。
通用表空間
如果我們沒有自己去創建,是沒有這個通用表空間的,需要通過CREATE TABLESPACE
來創建,我們可以為表指定表空間
CREATE TABLE [name] (......
)TABLESPACE [spacename];
這樣可以使得多個表共享一個ibd文件,避免ibd文件過多,當然,這些表依舊是獨立的,我們可以將許多小型的表集中存儲,可以減少磁盤碎片,簡化管理,并且利于批量的備份恢復。
撤銷表空間
在mysql實例初始化時會自動創建兩個默認的undo表空間,初始大小16M,用于存儲undo log日志,保證事務的原子性和一致性,主要用于支持事務的回滾操作。
臨時表空間
用于存儲用戶創建的臨時表,比如說多表查詢時會創建一個臨時表,我們在執行查詢/排序/GROUP BY等等的復雜操作時,可能會產生一個臨時表存儲中間結果,而這個臨時表就存儲在臨時表空間當中,查詢結束后,就會自動清理。
雙寫緩沖區
InnoDB引擎將數據頁從buffer pool刷新到磁盤前,先會將數據頁寫入到雙寫緩沖區文件,便于系統異常時恢復。
重做日志
redo log,用來實現事務的持久性,由重做日志緩沖區和重做日志文件兩部分組成,前者位于內存,后者位于磁盤,當事務提交之后,會將所有修改信息都存到該日志中,用于在刷新臟頁到磁盤發生錯誤時的數據恢復使用。
而我們的redo log是一個循環寫的機制,這一點和redis的AOF很像。
1.3 后臺線程
將內存中緩存池的數據在合適的時機刷新入磁盤中。
Master Thread
核心后臺線程,負責調度其他線程,還負責將緩沖池中的數據異步刷新到磁盤中,保持數據一致性,包括臟頁的刷新,合并插入緩存,undo頁的回收等。
IO Thread
在innodb引擎中,采取了AIO,也就是異步非阻塞IO,極大地提高了數據庫的性能,我們的IO Thread主要負責這些異步請求的回調。
- Read Thread:讀操作
- Write Thread:寫操作
- Log Thread:將日志緩沖區刷新到磁盤
- Insert Buffer Thread:將寫緩沖區的數據刷新到磁盤
Purge Thread
回收事務已經提交的Undo Log,事務提交之后,之前記錄的undo log不再使用,就用purge thread來回收
Page Cleaner Thread
協助Master Thread刷新臟頁到磁盤的線程,減輕核心后臺線程的壓力。
2. 事務原理
2.1 事務
前文我們知道,事務是密不可分的集合,具有ACID的特性,原子性,一致性,隔離性,持久性。
其中,原子性,一致性和持久性是由我們的redo log和undo log支持實現的;而隔離性是由我們InnoDB的鎖機制和MVCC(多版本并發控制)實現的。
2.2 redo log
重做日志,記錄事務提交時的數據頁的修改,用來實現持久性,由redo log buffer和redo log file兩部分組成,前者內存,厚澤磁盤,事務提交之后,會把所有修改信息都存到該日志文件中,用于刷新臟頁到磁盤,發生錯誤時進行數據恢復使用,大致流程如下:
- 客戶端向mysql服務端發起update操作
- mysql查詢buffer pool,發現沒有我們需要更新的數據。
- 啟動后臺線程,從后臺讀取我們需要的信息,隨后寫入到緩沖區,此時就可以直接對緩沖區進行操作,并且會將增刪改的數據寫入到redo log buffer當中,此時磁盤和內存數據不一致,稱之為臟頁。
- 那么我們如果定期將內存的數據刷新到磁盤,此時出錯了,如果沒有redo log buffer,我們的事務已經提交,說明了無法得到數據持久性保證,但是我們有了redo log,我們的redo log buffer會直接將修改的數據刷新到磁盤中的redo log file,如果出錯了,就會通過redo log file恢復數據,**為啥redo log buffer不直接刷新到真正的數據頁?**因為我們真正的數據頁刷新將會是隨機的IO,而直接記錄到日志上是順序寫,所以性能更高。
- 而我們的redo log日志是類似循環寫的模式,每隔一段時間都會被清理。
2.3 undo log
undo log日志解決原子性問題,叫做回滾日志,記錄數據被修改前的操作,它可以提供回滾和MVCC(多版本并發控制),undo log和redo log的記錄物理日志不一樣,他是邏輯日志,比如說我們insert一條信息,在undo log中就是delete的形式記錄,當我們執行rollback,就可以讀取到相應的內容進行回滾。
undo log在事務執行時產生,但是在事務提交時并不會立刻刪除,因為這些日志還可能用于MVCC,并且undo log采取段的方式時機管理和記錄,存在rollback段,內部包含1024個undo log。
3. MVCC(多版本并發控制)
3.1 當前讀
讀取的是記錄最新版本的記錄,會加鎖保證其他并發事務不會修改,我們在事務隔離級別可重復讀中使用事務,如果在進入事務之后,有數據被修改,那么我們讀取到的數據并不是最新的,也就不是當前讀,如果想要當前讀,我們可以執行 SELECT ... LOCK IN SHARE MODE
,初次之外,還可以使用 SELECT FOR UPDATE/INSERT/DELETE
來實現當前讀。
3.2 快照讀
簡單的selete就是我們的快照讀,比如我們在可重復讀的事務隔離級別中,就是典型的快照讀,不加鎖,是非阻塞讀,當然,在我們的串行化的事務隔離級別中,快照讀就會退化為當前讀,在read committed中,每次select都會生成一個快照讀。
3.3 MVCC
多版本并發控制,它會維護一個數據的多個版本,使得讀寫操作沒有沖突,快照讀為mysql實現mvcc提供了一個重要的非阻塞讀功能,當,MVCC還依賴于數據庫記錄的三個隱式字段,undo log, readview。
隱式字段
當我們創建表的時候,會指定相應的表結構,包括id,name,age的字段,我們可以顯式的看到這些字段,但是實際上,mysql還未我們隱藏了三個額外增加的字段:
DB_TRX_ID:最近修改事務ID,記錄最后插入或修改這一行的事務ID。
DB_ROLL_PTR:回滾指針,指向這條記錄的上一個版本,配合undo log。
DB_ROW_ID:隱藏主鍵,如果表結構沒有主鍵,就會生成這個隱藏字段。
undo log
我們之前提過,undo log在一定情況下不會被立即刪除,比如update和delete,產生的undo log不僅在回滾時需要,快照讀的時候也需要,所以不會被立即刪除,而insert只會在回滾的時候才需要,所以在事務提交的時候可以立即刪除,下面來看看實例分析:
借用一下黑馬程序員的圖
以上的示例列舉的進行修改操作時的MVCC的版本鏈的控制,而執行查詢操作的時候,則涉及到了另一個組件readView
readview
讀視圖,是快照讀SQL執行時MVCC提取數據的依據,記錄并維護當前活躍的事務(未提交)的id,包含四個核心字段:
m_ids:當前活躍的事務ID的集合
min_trx_id:最小的活躍事務ID
max_trx_id:預分配事務ID,當前最大事務ID + 1
creator_trx_id:readview創建者的事務ID
版本鏈訪問規則:
trx_id(當前事務ID) == creator_trx_id:ok,說明數據是當前事務更改的。
trx_id < min_trx_id:ok,說明數據已經提交。
trx_id > max_trx_id:no,該事務是在readview生成后才開啟的。
min_trx_id <= trx_id <= max_trx_id && trx_id 不存在于m_ids中:ok,數據已經提交。
readview生成時機在不同隔離級別有所不同:
read committed:事務每次執行快照讀時,都會生成。(這里建議去看看黑馬的視頻里面的分析)
repeatable read:在事務第一次執行快照讀時生成readview,后續復用這個readview。
ax_trx_id && trx_id 不存在于m_ids中:ok,數據已經提交。
readview生成時機在不同隔離級別有所不同:
read committed:事務每次執行快照讀時,都會生成。(這里建議去看看黑馬的視頻里面的分析)
repeatable read:在事務第一次執行快照讀時生成readview,后續復用這個readview。
這樣,原理就介紹完畢了。
下午把lab做完了,繼續學可能效率不太高,就把之前欠的賬補上了。