MySQL(二)InnoDB的内存结构和特性
?
目錄
In-Memory Structures
緩沖池?Buffer Pool
更新緩沖Change Buffer
Adaptive Hash Index
(redo)Log Buffer
總結
磁盤結構On-Disk Structures
存儲結構
后臺線程
BinLog
兩階段提交實現
MySQL區別于其他數據庫的最為重要的特點就是其插件式的表存儲引擎。而在眾多存儲引擎中,InnoDB是最為常用的存儲引擎。從MySQL5.5.8版本開始,InnoDB存儲引擎是默認的存儲引擎。
InnoDB存儲引擎支持事務,其特點是行鎖設計、支持外鍵,并支持非鎖定讀,即默認讀操作不會產生鎖。InnoDB結構主要可分為內存結構和磁盤結構
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
In-Memory Structures
內存結構可以分成多個部分:?Buffer Pool、Change Buffer、Adaptive Hash Index,(redo)log buffer
緩沖池?Buffer Pool
InnoDB存儲引擎是基于磁盤存儲的,也就是說數據都是存儲在磁盤上的,由于 CPU 速度和磁盤速度之間的有非常大的出入, InnoDB 引擎使用緩沖池技術來提高數據庫的整體性能。
InnoDB操作數據有一個最小的邏輯單位,叫做頁。InnoDB會把磁盤讀到的頁放到一塊內存區域里面。這個內存區域就叫Buffer Pool。Buffer Pool緩存的是頁面信息,包括數據頁、索引頁。下一次再讀取相同的頁時,就先判斷是否已經在緩沖池里面,如果是,就直接讀取,不用再訪問磁盤
對于數據庫中頁的修改操作,首先修改在緩沖池中的頁,然后再以一定的頻率刷新到磁盤上。
為了提高讀取操作的效率,緩沖池分為多個頁,這些頁可以容納多行數據。為了提高緩存管理的效率,緩沖池是以頁的鏈表方式實現的,使用LRU算法的將很少使用的數據頁從緩存中刪除。
Buffer Pool默認大小是128M(134217728字節),可以通過SHOW VARIABLES like'%innodb_buffer_pool%'; 查看
更新緩沖Change Buffer
一般情況下,主鍵是行唯一的標識符。通常應用程序中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此,插入聚集索引一般是順序的,不需要磁盤的隨機讀取。因為,對于此類情況下的插入,速度還是非常快的。(如果主鍵類是UUID這樣的類,那么插入和輔助索引一樣,也是隨機的。)
如果索引是非聚集的且不唯一。在進行插入操作時,數據的存放對于非聚集索引葉子節點的插入不是順序的,這時需要離散地訪問非聚集索引頁,由于需要隨機讀取頁,從而導致插入操作性能下降。
當需要更新一個數據頁時,如果數據頁在BufferPool中不存在,那么就需要從磁盤加載到內存,再對內存的數據頁進行操作。也就是說,如果沒有命中緩沖池,至少要產生一次磁盤IO。對于這種情形,InnoDB也有對應的優化方式,就是Change Buffer
如果這個數據頁不存在唯一索引,不需要擔心出現數據重復的情況,那么就不需要從磁盤加載索引頁進行唯一性檢查。這樣我們就可以先把想要修改的內容存放在內存的緩沖池中,從而提升更新語句的執行速度。
這一塊區域就是 Change Buffer , 5.5 之前叫Insert Buffer 插入緩沖.
最后再把?Change Buffer 里的數據頁批量寫入到磁盤,這個過程稱之為merge,在以下幾個情形下會觸發merge操作:
(1)redo log寫滿時,此時需要將checkpoint向前推進,推進的這部分日志對應的臟頁需要刷入到磁盤,此時所有的更新全部阻塞,必須待刷一部分臟頁后才能更新。
(2)系統內存不足時,需要將一部分數據頁淘汰掉,如果淘汰的是臟頁,需要先將臟頁同步到磁盤。
(3) MySQL認為空閑的時間
(4) mysql正常關閉之前,會把所有臟頁刷入磁盤
如果數據庫大部分索引都是非唯一索引,并且業務是寫多讀少,不會在寫數據后立刻讀取,就可以使用Change Buffer。如果是寫多讀少的業務,可以適當調大ChangerBuffer占Buffer Pool的比例,默認值是25%。
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
Adaptive Hash Index
哈希是一種非常快的查找方法,在一般情況時間復雜度為O(1)。而B+樹的查找次數,取決于B+樹的高度,在生產環境中,B+樹的高度一般為3-4層,只需要查詢3-4次。
InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature
直接翻譯過來就是:InnoDB內部使用哈希索引來實現自適應哈希索引特性。
這句話的意思是 InnoDB 只支持顯式創建 B+樹 索引,對于一些熱點數據頁,InnoDB會自動建立自適應Hash索引,也就是當InnoDB注意到某些索引值被使用的非常頻繁時,它會在內存中基于B+樹索引上再創建一個hash索引,這樣就讓B+樹索引也具有hash索引的一些優點。
這個過程對于客戶端是不可控制的,隱藏的,由InnoDB自己內部完成
(redo)Log Buffer
緩沖池的存在提高了InnoDB查詢和更新數據的速度,但是不可能避免的也會引發一定的安全性問題,當緩沖池的數據比磁盤數據要新時,如果不及時把緩沖池中新版本的頁數據同步到磁盤,一旦出現斷電等事故,緩沖池中更新的數據就會丟失。但是如果每次一個頁發送變化,就進行刷新,那么性能消耗是非常大的。
為了避免這個問題, InnoDB把所有對頁面的修改操作專門寫入一個日志文件,并且在數據庫啟動時從這個文件進行恢復操作(實現crash-safe)——用它來實現事務的持久性。
這種日志和磁盤配合的整個過程,其實就是 MySQL 里的 WAL 技術(Write-Ahead Logging),其關鍵點就是先寫日志,再寫磁盤,即當事務提交時,先寫redo log日志,然后再將臟頁(當內存數據頁和磁盤數據頁上的內容不一致時,我們稱這個內存頁為臟頁)寫入磁盤。如果發生宕機導致數據丟失,就可以通過redo log日志來進行數據恢復。
redo log對應于/var/lib/mysql/目錄下的ib_logfile0和ib_logfile1,每個大小為48M
同樣是寫磁盤,為什么InnoDB選擇寫入到redo log里,而不是直接寫入到磁盤文件?
這是因為順序IO比隨機IO更快?理解順序IO和隨機IO
刷盤是隨機I/O,而日志記錄是順序I/O,順序I/O的效率更高。因此先把修改寫入日志,這樣可以延遲刷盤時機又不用擔心宕機導致數據丟失,進而提升系統吞吐量。
為了減少寫日志的磁盤IO , redo log也不是每一次數據更新都直接寫入磁盤,在Buffer Pool里面有一塊內存區域(Log Buffer)專門用來保存即將要寫入日志文件的數據,默認大小為16M
Log Buffer寫入到redo log的時機,由一個參數控制,默認是1(SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';)
注意:在我們寫入數據到磁盤的時候,操作系統本身是有緩存的。這里的flush操作就是把操作系統緩沖區的數據寫入到磁盤。
注意:這里所說的log file并不是磁盤上的物理日志文件,而是操作系統緩存中的log file
這里的redo log的大小是固定的,當不斷加入新的日志時,前面的內容會被覆蓋
這里write pos表示日志當前記錄的位置,當ib_logfile_4寫滿后,會從ib_logfile_1從頭開始記錄;check point當前日志將要被擦除的位置,如果日志記錄的修改已經被寫進數據庫文件,checkpoint會將日志上的相關記錄擦除掉,并向前移動,所以write pos->checkpoint之間的部分是redo log空著的部分,用于記錄新的記錄。當write pos跟check point重疊,說明redo log已經寫滿,這時候需要同步redo log到磁盤中
總結
緩沖池?Buffer Pool:緩存訪問的頁數據
更新緩沖Change Buffer:對于不更新唯一索引的DML,緩存到change buffer然后批量寫入到磁盤
Adaptive Hash Index:由InnoDB自己生成,對熱點數據頁加上哈希索引提高查詢效率
(redo)Log Buffer:緩存每次更新操作的日志,定時寫入到redo log中,可用于數據恢復
磁盤結構On-Disk Structures
磁盤結構里面主要是各種各樣的表空間,叫做Table space。表空間可以看做是InnoDB 存儲引擎邏輯結構的最高層,所有的數據都存放在表空間中。InnoDB的表空間分為5大類
系統表空間 system tablespace
在默認情況下 InnoDB 存儲引擎有一個共享表空間(對應文件/var/lib/mysql/ibdata1),也叫系統表空間。
InnoDB系統表空間包含InnoDB數據字典和雙寫緩沖區,ChangeBuffer和Undo Logs,如果表沒有指定自己的表空間(file-per-table),那么系統表空間也會包含用戶創建的表和索引數據。
undo logs后面介紹,可以有對應的表空間---undo log tablespace
數據字典:?
?? ?InnoDB有自己的表緩存,可以稱為表定義緩存或者數據字典。當InnoDB打開一張表,就增加一個對應的對象到數據字典。由內部系統表組成,存儲表和索引的元數據(定義信息)。數據字典是對數據庫中的數據、庫對象、表對象等的元信息的集合。MySQL INFORMATION_SCHEMA庫中保存的信息也可以稱為MySQL的數據字典。
獨占表空間 file-per-table tablespaces
我們可以讓每張表獨占一個表空間。這個開關通過innodb_file_per_table設置,默認開啟。
SHOW VARIABLES LIKE 'innodb_file_per_table';
開啟后,則每張表會開辟一個表空間,這個文件就是數據目錄下的 ibd文件(例如/var/lib/mysql/db0/user.ibd),存放表的索引和數據
通用表空間 general tablespaces
通用表空間也是一種共享的表空間,跟ibdata1類似。
可以創建一個通用的表空間,用來存儲不同數據庫的表,數據路徑和文件可以自定義。
語法:
create tablespace ?tschenpp add datafile '/var/lib/mysql/tschenpp.ibd' file_block_size=16K engine=innodb;
在創建表的時候可以指定表空間,用ALTER修改表空間可以轉移表空間。
create table t1 (id integer) tablespace tschenpp ;
刪除表空間需要先刪除里面的所有表:
drop t1 ; drop tablespace tschenpp ;
臨時表空間 temporary tablespaces
存儲臨時表的數據,包括用戶創建的臨時表,和磁盤的內部臨時表。對應數據目錄下的ibtmp1文件。當數據服務器正常關閉時,該表空間被刪除,下次重新產生
undo log tablespace
undo log(也稱為撤銷日志或回滾日志)記錄了事務發生之前的數據狀態(不考慮DQL語句) 。如果操作數據時出現異常,可以使用undo log來實現回滾操作(保持原子性)。還可以用于MVCC(實現非鎖定讀),讀取一行記錄時,若該行已被其他事務鎖定,則通過undo log 讀取之前的版本。
在執行回滾的時候,僅僅是將數據從邏輯上恢復至事務之前的狀態,而不是從物理頁面上實現操作的,屬于邏輯格式的日志。可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄,會根據每行記錄進行記錄
undo Log 的數據默認在系統表空間ibdata1 文件中,也可以獨自創建一個undo表空間。
redo Log和undo Log與事務密切相關,統稱為事務日志。undo log記錄某 數據 被修改 前 的值,可以用來在事務失敗時進行 rollback;redo log記錄某 數據塊 被修改 后 的值,可以用來恢復未寫入 data file 的已成功提交事務的數據。
比如某一時刻數據庫宕機了,此時有兩個事務,一個事務已經提交,另一個事務正在處理中。數據庫重啟的時候就要根據日志分別進行數據寫入及回滾,把已提交事務的更改寫到數據文件,未提交事務的更改恢復到事務開始前的狀態。即,當數據 crash-recovery 時,通過 redo log 將所有已經在存儲引擎內部提交的事務數據恢復,所有已經 prepared 但是沒有 commit 的 事務(什么叫做preoared,commit的事務,這里涉及到binlog和redo log的兩階段提交,下面會介紹下)將會通過?undo log 做回滾。
雙寫緩沖Doublewrite Buffer(InnoDB的一大特性)
InnoDB的頁和操作系統的頁大小不一致,InnoDB頁大小一般為16K,操作系統頁大小為4K,InnoDB的頁寫入到磁盤時,一個頁需要分4次寫。
如果存儲引擎正在寫入頁的數據到磁盤時發生了宕機,可能出現頁只寫了一部分的情況,比如只寫了4K,就宕機了,這種情況叫做部分寫失效(partial page write),可能會導致數據丟失。
The doublewrite buffer is a storage area in the system tablespace where?InnoDB?writes pages that are flushed from the buffer pool before writing them to their proper positions in the data file. Only after flushing and writing pages to the doublewrite buffer does?InnoDB?write pages to their proper positions. If there is an operating system, storage subsystem, or?mysqld?process crash in the middle of a page write,?InnoDB?can find a good copy of the page from the doublewrite buffer during crash recovery.
前面說到redo log可以用來做數據恢復,不過如果此時數據頁是損壞的(只寫入了部分頁數據到磁盤文件),那么使用redo log是無法恢復數據的,因為redo log記錄的是頁的物理修改。此時我們就需要一個完整的頁的副本。Doublewrite Buffer就是在寫入頁數據到磁盤上之前,先將頁數據完整寫入到磁盤和內存的double write區,只有寫入和刷新成功后,才會將頁數據寫入到數據文件。那么如果出現了寫入失敗,就先用頁的副本來還原這個頁,然后再應用redo log。這個就是Double Write,通過它保證了數據頁的可靠性
double write 由兩部分組成,一部分是內存的double write,一個部分是磁盤上的double write。雖然數據有兩次寫入操作,由于Doublewrite Buffer在表空間中是連續的區域,因此,I/O開銷并不大,只需調用一次fsync()。
為什么redo log的日志寫入不需要double write的支持?
因為redolog寫入的單位就是512字節,也就是磁盤IO的最小單位,所以無所謂數據損壞。
存儲結構
MySQL的存儲結構分為5級:表空間、段、簇、頁、行
表空間 TableSpace
前面提到過表空間可以看做是InnoDB 存儲引擎邏輯結構的最高層,所有的數據都存放在表空間中。分為:系統表空間、獨占表空間、通用表空間、臨時表空間、Undo表空間。
段 Segment
表空間是由各個段組成的,常見的段有數據段、索引段、回滾段等,段是一個邏輯的概念。一個ibd文件(獨立表空間文件)會由很多個段組成。
創建一個索引會創建兩個段(即是說,一個表的段數就是索引的個數乘以2。),一個是索引段:non-leaf node segment,一個是數據段: leaf node segment。索引段管理非葉子節點的數據。數據段管理葉子節點的數據。
簇 Extent
一個段(Segment)又由很多的簇(也可以叫區)組成,每個區的大小是1MB(64個連續的頁)。
每一個段至少會有一個簇,一個段所管理的空間大小是無限的,可以一直擴展下去,但是擴展的最小單位就是簇(1M)
頁Page
頁是InnoDB磁盤管理的最小單位,每個頁默認大小為16KB,通過參數innodb_page_size可以修改默認頁的大小。
一個簇中有64個連續的頁。 (1MB/16KB=64)。這些頁面在物理上和邏輯上都是連續的。
一個表空間最多擁有2^32個頁,默認情況下一個頁的大小為16KB,也就是說一個表空間最多存儲64TB的數據。
行 Row
InnoDB 存儲引擎是面向行的(row-oriented),也就是說數據的存放按行進行存放。每個頁存放的行記錄也是有硬性定義的,最多允許存放16KB/2-200,即7992行記錄(據說是由內核定義的)。
后臺線程
內存和磁盤之間,工作著很多后臺線程
后臺線程的主要作用是負責刷新內存池中的數據和把修改的數據頁刷新到磁盤。后臺線程分為:master thread,IO thread,purge thread,page cleaner thread。
- master thread負責刷新緩存數據到磁盤并協調調度其它后臺進程。
- IO thread 分為 insert buffer、 log、 read、 write進程。分別用來處理insert buffer、重做日志、讀寫請求的IO回調。
- purge thread用來回收undo 頁。
- page cleaner thread用來刷新臟頁
BinLog
除了 InnoDB 架構中的日志文件,MySQL 的 Server 層也有一個日志文件,叫做binlog,它可以被所有的存儲引擎使用
binlog以事件的形式記錄了所有的DDL和DML語句(因為它記錄的是操作而不是數據值,屬于邏輯日志),可以用來做主從復制和數據恢復。
跟redo log不一樣,它的文件內容是可以追加的,沒有固定大小限制。在開啟了binlog功能的情況下,我們可以把binlog導出成SQL語句,把所有的操作重新執行一遍,來實現數據的恢復。
binlog的另一個功能就是用來實現主從復制,它的原理就是從服務器讀取主服務器的binlog,然后執行一遍。
兩階段提交實現
這個兩階段提交不是分布式事務的兩階段提交,而是在開啟binlog之后,redo與binlog的兩階段提交。 兩階段提交:首先redo log寫入(?prepare狀態),然后寫binlog,最后redo log 改為commit狀態.
如下更新SQL的執行流程為:深色的表示在執行器中執行的,淺色的表示在InnoDB執行引擎內部執行
為什么使用兩階段提交協議呢,這是為了當數據庫發生了意外情況,宕機、斷點、重啟等等,可以保證使用binLog恢復數據和當時數據狀態一致;
具體情況下的策略如下:
bin log有記錄,redo log狀態commit:正常完成的事務,不需要恢復
bin log有記錄,redo log狀態prepare:在bin log寫完提交事務之前的crash,恢復操作:提交事務
bin log無記錄,redo log狀態prepare:在binlog寫完之前的crash,恢復操作:使用undo log回滾事務
bin log無記錄,redo log無記錄:在redolog寫之前crash,恢復操作:使用undo log回滾事務
bin log和redo log的區別:
-
redo log是InnoDB引擎特有的;binlog是MySQL的Server層實現的,所有引擎都可以使用
-
redo log是物理日志,記錄的是"在某個數據頁上做了什么修改";binlog是邏輯日志,記錄的是這個語句的原始邏輯,比如"給ID=2這一行的c字段加1 "
-
redo log是循環寫的,空間固定會用完;binlog是可以追加寫入的。"追加寫"是指binlog文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志
參考:
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
https://www.cnblogs.com/binyang/p/11260126.html
總結
以上是生活随笔為你收集整理的MySQL(二)InnoDB的内存结构和特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Auto packing the rep
- 下一篇: 解决IDEA本地仓库有jar包却无法引用