在MySQL中的引擎一文中說了宛畦,我們在幾乎所有的情況下其實(shí)用的都是InnoDB引擎次和,這里我們就重點(diǎn)再看一下這個引擎踏施,包括他的存儲結(jié)構(gòu)畅形,線程模型和數(shù)據(jù)文件。
我們可以通過show engine innodb status \G;(\G只是表示輸出結(jié)果縱向表格輸出)命令可以查看innodb引擎的的一些基本信息竖席,如下圖:
上圖中可以看到有線程束析、事務(wù)员寇、文件IO丁恭,緩沖區(qū)牲览,內(nèi)存,日志等等一些信息庸毫,我們把這些信息分為存儲結(jié)構(gòu)飒赃、線程模型和數(shù)據(jù)文件三類载佳,以下分別說明一下蔫慧。
存儲結(jié)構(gòu)
InnoDB的存儲引擎姑躲,其結(jié)構(gòu)從大的劃分來講分為內(nèi)存區(qū)域和磁盤區(qū)域兩部分:
InnoDB的內(nèi)存區(qū)域
內(nèi)存結(jié)構(gòu)主要包括Buffer Pool、Change Buffer橄仍、Adaptive Hash Index和Log Buffer四大組件侮繁,這里分別看一下:
1. Buffer Pool(緩沖池宪哩,簡稱BP)
BP以Page頁為單位锁孟,默認(rèn)大小16K品抽,BP的底層采用鏈表數(shù)據(jù)結(jié)構(gòu)管理Page圆恤。在InnoDB訪問表記錄和索引時(shí)會在Page頁中緩存,以后使用可以減少磁盤IO操作淡喜,提升效率诵闭。以下我們看一下BP的頁管理瘟芝,內(nèi)存的淘汰算法LRU以及緩沖池的一些配置參數(shù):
1)Page管理機(jī)制
Page根據(jù)狀態(tài)可以分為三種類型:
free page : 空閑page模狭,未被使用
clean page:被使用page嚼鹉,數(shù)據(jù)沒有被修改過
dirty page:臟頁锚赤,被使用page线脚,數(shù)據(jù)被修改過姊舵,頁中數(shù)據(jù)和磁盤的數(shù)據(jù)產(chǎn)生了不一致
針對上述三種page類型,InnoDB通過三種鏈表結(jié)構(gòu)來維護(hù)和管理:
free list :表示空閑緩沖區(qū)史飞,管理free page
flush list:表示需要刷新到磁盤的緩沖區(qū)仰税,管理dirty page吐绵,內(nèi)部page按修改時(shí)間排序拦赠。臟頁數(shù)據(jù)既存在于flush鏈表荷鼠,也在LRU鏈表中榔幸,但是兩種互不影響牍疏,LRU鏈表負(fù)責(zé)管理page的可用性和釋放鳞陨,而flush鏈表負(fù)責(zé)管理臟頁的刷盤操作。
lru list:表示正在使用的緩沖區(qū)歼狼,管理clean page和dirty page趟咆,緩沖區(qū)以midpoint為基點(diǎn)值纱,前面鏈表稱為new列表區(qū)虐唠,存放經(jīng)常訪問的數(shù)據(jù),占63%庶近;后面的鏈表稱為old列表區(qū)翁脆,存放使用較少數(shù)據(jù),占37%罢缸。
2)改進(jìn)型LRU算法維護(hù)
普通LRU:末尾淘汰法,新數(shù)據(jù)從鏈表頭部加入敷鸦,釋放空間時(shí)從末尾淘汰值依。
改進(jìn)型LRU:鏈表分為new和old兩個部分愿险,加入元素時(shí)并不是從表頭插入辆亏,而是從中間midpoint位置插入褒链,如果數(shù)據(jù)很快被訪問,那么page就會向new列表頭部移動兵迅,如果數(shù)據(jù)沒有被訪問刻恭,會逐步向old尾部移動,等待淘汰骑科。
每當(dāng)有新的page數(shù)據(jù)讀取到buffer pool時(shí)咆爽,InnoDb引擎會判斷是否有空閑頁斗埂,是否足夠,如果有就將free page從free list列表刪除凫海,放入到LRU列表中呛凶。沒有空閑頁,就會根據(jù)LRU算法淘汰LRU鏈表默認(rèn)的頁盐碱,將內(nèi)存空間釋放分配給新的頁把兔。
3)Buffer Pool配置參數(shù)
show variables like '%innodb_page_size%'; //查看page頁大小
show variables like '%innodb_old%'; //查看lru list中old列表參數(shù)
show variables like '%innodb_buffer%'; //查看buffer pool參數(shù)
建議:將innodb_buffer_pool_size設(shè)置為總內(nèi)存大小的60%-80%,
innodb_buffer_pool_instances可以設(shè)置為多個瓮顽,這樣可以避免緩存爭奪县好。
2. Change Buffer(寫緩沖區(qū),簡稱CB)
在進(jìn)行DML操作時(shí),如果BP沒有相應(yīng)的Page數(shù)據(jù),并不會立刻將磁盤加載到緩沖池泪勒,而是在CB記錄緩沖變更,等未來數(shù)據(jù)被讀取時(shí),再將數(shù)據(jù)合并恢復(fù)到BP中。ChangeBuffer占用BufferPool空間谒出,默認(rèn)占25%,最大允許占50%,可以通過參數(shù)innodb_change_buffer_max_size設(shè)置上煤,具體調(diào)多大要根據(jù)讀寫業(yè)務(wù)量。
當(dāng)更新一條記錄時(shí)组橄,如果該記錄在BufferPool存在,就直接在BufferPool修改费奸,也就是一次內(nèi)存操作糙麦。如果該記錄在BufferPool不存在(沒有命中)冶匹,會直接在ChangeBuffer進(jìn)行一次內(nèi)存操作,不用再去磁盤查詢數(shù)據(jù)泄隔,避免一次磁盤IO。當(dāng)下次查詢記錄時(shí),會先進(jìn)行磁盤讀取诱告,然后再從ChangeBuffer中讀取信息合并,最終載入BufferPool中搜贤。
這里要注意寫緩沖區(qū)只適用于非唯一普通索引頁饺蔑。如果在索引設(shè)置唯一性惶室,在進(jìn)行修改時(shí)趾痘,InnoDB必須要做唯一性校驗(yàn)世分,因此必須查詢磁盤,這樣就要做一次IO操作旗唁。會直接將記錄查詢到BufferPool中检疫,然后在緩沖池修改,不會在ChangeBuffer操作。
3. Adaptive Hash Index(自適應(yīng)哈希索引)
用于優(yōu)化對BP數(shù)據(jù)的查詢蟆盹。InnoDB存儲引擎會監(jiān)控對表索引的查找归榕,如果觀察到建立哈希索引可以帶來速度的提升,則建立哈希索引吱涉,所以稱之為自適應(yīng)刹泄。InnoDB存儲引擎會自動根據(jù)訪問的頻率和模式來為某些頁建立哈希索引外里。
4. Log Buffer(日志緩沖區(qū))
日志緩沖區(qū)用來保存要寫入磁盤上log文件(Redo/Undo)的數(shù)據(jù),日志緩沖區(qū)的內(nèi)容定期刷新到磁盤log文件中特石。日志緩沖區(qū)滿時(shí)會自動將其刷新到磁盤盅蝗,當(dāng)遇到BLOB或多行更新的大事務(wù)操作時(shí),增加日志緩沖區(qū)可以節(jié)省磁盤I/O姆蘸。
LogBuffer主要是用于記錄InnoDB引擎日志墩莫,在DML操作時(shí)會產(chǎn)生Redo和Undo日志。
LogBuffer空間滿了逞敷,會自動寫入磁盤狂秦。可以通過將innodb_log_buffer_size參數(shù)調(diào)大推捐,減少磁盤IO頻率裂问。
innodb_flush_log_at_trx_commit參數(shù)控制日志刷新行為,默認(rèn)為1
0 : 每隔1秒寫日志文件和刷盤操作(寫日志文件LogBuffer-->OS cache玖姑,刷盤OS
cache-->磁盤文件)愕秫,最多丟失1秒數(shù)據(jù)
1:事務(wù)提交,立刻寫日志文件和刷盤焰络,數(shù)據(jù)不丟失戴甩,但是會頻繁IO操作
2:事務(wù)提交,立刻寫日志文件闪彼,每隔1秒鐘進(jìn)行刷盤操作
InnoDB的磁盤區(qū)域
InnoDB磁盤主要包含Tablespaces甜孤,InnoDB Data Dictionary,Doublewrite Buffer畏腕、Redo Log和Undo Logs缴川,這里我們看一下前面三個區(qū)域,后面兩個我們在MySQL日志的學(xué)習(xí)中再討論描馅。
1. 表空間(Tablespaces)
表空間用于存儲表結(jié)構(gòu)和數(shù)據(jù)把夸。表空間又分為系統(tǒng)表空間、獨(dú)立表空間铭污、通用表空間恋日、臨時(shí)表空間、Undo表空間等多種類型嘹狞;
1.1 系統(tǒng)表空間(The System Tablespace)
包含InnoDB數(shù)據(jù)字典岂膳,Doublewrite Buffer,Change Buffer磅网,Undo Logs的存儲區(qū)域谈截。系統(tǒng)表空間也默認(rèn)包含任何用戶在系統(tǒng)表空間創(chuàng)建的表數(shù)據(jù)和索引數(shù)據(jù)。系統(tǒng)表空間是一個共享的表空間因?yàn)樗潜欢鄠€表共享的。
該空間的數(shù)據(jù)文件通過參數(shù)innodb_data_file_path控制簸喂,默認(rèn)值是ibdata1:12M:autoextend(文件名為ibdata1毙死、12MB、自動擴(kuò)展)娘赴。
1.2 獨(dú)立表空間(File-Per-Table Tablespaces)
獨(dú)立表空間是一個單表表空間规哲,默認(rèn)是開啟的,該表創(chuàng)建于自己的數(shù)據(jù)文件中诽表,而非創(chuàng)建于系統(tǒng)表空間中唉锌。當(dāng)innodb_file_per_table選項(xiàng)開啟時(shí),表將被創(chuàng)建于表空間中竿奏。否則袄简,innodb將被創(chuàng)建于系統(tǒng)表空間中。每個表文件表空間由一個.ibd數(shù)據(jù)文件代表泛啸,該文件默認(rèn)被創(chuàng)建于數(shù)據(jù)庫目錄中绿语。表空間的表文件支持動態(tài)(dynamic)和壓縮(commpressed)行格式。
1.3通用表空間(General Tablespaces)
通用表空間為通過create tablespace語法創(chuàng)建的共享表空間候址。通用表空間可以創(chuàng)建于mysql數(shù)據(jù)目錄外的其他表空間吕粹,其可以容納多張表,且其支持所有的行格式岗仑。
CREATE TABLESPACE ts1 ADD DATAFILE ts1.ibd Engine=InnoDB; //創(chuàng)建表空間ts1
CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1; //將表添加到ts1表空間
1.3 撤銷表空間(Undo Tablespaces)
撤銷表空間由一個或多個包含Undo日志文件組成匹耕。在MySQL 5.7版本之前Undo占用的是System Tablespace共享區(qū),從5.7開始將Undo從System Tablespace分離了出來荠雕。
InnoDB使用的undo表空間由innodb_undo_tablespaces配置選項(xiàng)控制稳其,默認(rèn)為0。參數(shù)值為0表示使用系統(tǒng)表空間ibdata1;大于0表示使用undo表空間undo_001炸卑、undo_002等既鞠。
1.4 臨時(shí)表空間(Temporary Tablespaces)
CREATE TABLESPACE ts1 ADD DATAFILE ts1.ibd Engine=InnoDB; //創(chuàng)建表空間ts1
CREATE TABLE t1 (c1 INT PRIMARY KEY) TABLESPACE ts1; //將表添加到ts1表空間
分為session temporary tablespaces 和global temporary tablespace兩種。session temporary tablespaces 存儲的是用戶創(chuàng)建的臨時(shí)表和磁盤內(nèi)部的臨時(shí)表盖文。global temporary tablespace儲存用戶臨時(shí)表的回滾段(rollback segments )嘱蛋。mysql服務(wù)器正常關(guān)閉或異常終止時(shí),臨時(shí)表空間將被移除五续,每次啟動時(shí)會被重新創(chuàng)建洒敏。
2. 數(shù)據(jù)字典(InnoDB Data Dictionary)
InnoDB數(shù)據(jù)字典由內(nèi)部系統(tǒng)表組成,這些表包含用于查找表返帕、索引和表字段等對象的元數(shù)據(jù)桐玻。
元數(shù)據(jù)物理上位于InnoDB系統(tǒng)表空間中篙挽。由于歷史原因荆萤,數(shù)據(jù)字典元數(shù)據(jù)在一定程度上與InnoDB表元數(shù)據(jù)文件(.frm文件)中存儲的信息重疊。
3. 雙寫緩沖區(qū)(Doublewrite Buffer)
雙寫緩沖區(qū)位于系統(tǒng)表空間,是一個存儲區(qū)域链韭。
在BufferPage的page頁刷新到磁盤真正的位置前偏竟,會先將數(shù)據(jù)存在Doublewrite 緩沖區(qū)。
如果在page頁寫入過程中出現(xiàn)操作系統(tǒng)敞峭、存儲子系統(tǒng)或mysqld進(jìn)程崩潰踊谋,InnoDB可以在崩潰恢復(fù)期間從Doublewrite 緩沖區(qū)中找到頁面的一個好備份。
如果要禁用Doublewrite 緩沖區(qū)旋讹,可以將innodb_doublewrite設(shè)置為0殖蚕。
在大多數(shù)情況下,默認(rèn)情況下啟用雙寫緩沖區(qū)沉迹,使用Doublewrite 緩沖區(qū)時(shí)睦疫,建議將innodb_flush_method設(shè)置為O_DIRECT。這里的MySQL的innodb_flush_method這個參數(shù)控制著innodb數(shù)據(jù)文件及redo log的打開鞭呕、刷寫模式蛤育。他有三個值:fdatasync(默認(rèn)),O_DSYNC葫松,O_DIRECT瓦糕。設(shè)置O_DIRECT表示數(shù)據(jù)文件寫入操作會通知操作系統(tǒng)不要緩存數(shù)據(jù),也不要用預(yù)讀腋么,直接從Innodb Buffer寫到磁盤文件咕娄。
默認(rèn)的fdatasync意思是先寫入操作系統(tǒng)緩存,然后再調(diào)用fsync()函數(shù)去異步刷數(shù)據(jù)文件與redo log的緩存信息党晋。
新版本存儲結(jié)構(gòu)的改變
MySQL在5.7和后續(xù)的8.0版本中的存儲結(jié)構(gòu)谭胚,做了一定改變,如下圖所示:
MySQL 5.7 版本
1)將 Undo日志表空間從共享表空間 ibdata 文件中分離出來未玻,可以在安裝 MySQL 時(shí)由用戶自行指定文件大小和數(shù)量灾而。
2)增加了 temporary 臨時(shí)表空間,里面存儲著臨時(shí)表或臨時(shí)查詢結(jié)果集的數(shù)據(jù)扳剿。
3)Buffer Pool 大小可以動態(tài)修改旁趟,無需重啟數(shù)據(jù)庫實(shí)例。
MySQL 8.0 版本
1)將InnoDB表的數(shù)據(jù)字典和Undo都從共享表空間ibdata中徹底分離出來了庇绽,以前需要ibdata中數(shù)據(jù)字典與獨(dú)立表空間ibd文件中數(shù)據(jù)字典一致才行锡搜,8.0版本就不需要了。
2)emporary 臨時(shí)表空間也可以配置多個物理文件瞧掺,而且均為 InnoDB 存儲引擎并能創(chuàng)建索引耕餐,這樣加快了處理的速度。
3)用戶可以像 Oracle 數(shù)據(jù)庫那樣設(shè)置一些表空間辟狈,每個表空間對應(yīng)多個物理文件肠缔,每個表空間可以給多個表使用夏跷,但一個表只能存儲在一個表空間中。
4)將Doublewrite Buffer從共享表空間ibdata中也分離出來了明未。
線程模型
InnoDB的線程模型分為IO Thread槽华、Purge Thread、Page Thread和Master Thread趟妥,如下圖所示:
IO Thread
在InnoDB中使用了大量的AIO(Async IO)來做讀寫處理猫态,這樣可以極大提高數(shù)據(jù)庫的性能。在InnoDB1.0版本之前共有4個IO Thread披摄,分別是write亲雪,read,insert buffer和log thread疚膊,后來版本將read thread和write thread分別增大到了4個匆光,一共有10個了。
read thread : 負(fù)責(zé)讀取操作酿联,將數(shù)據(jù)從磁盤加載到緩存page頁终息。4個
write thread:負(fù)責(zé)寫操作,將緩存臟頁刷新到磁盤贞让。4個
log thread:負(fù)責(zé)將日志緩沖區(qū)內(nèi)容刷新到磁盤周崭。1個
insert buffer thread :負(fù)責(zé)將寫緩沖內(nèi)容刷新到磁盤。1個
Purge Thread
事務(wù)提交之后喳张,其使用的undo日志將不再需要续镇,因此需要Purge Thread回收已經(jīng)分配的undo頁。
show variables like '%innodb_purge_threads%';
Page Cleaner Thread
作用是將臟數(shù)據(jù)刷新到磁盤销部,臟數(shù)據(jù)刷盤后相應(yīng)的redo log也就可以覆蓋摸航,即可以同步數(shù)據(jù),又能達(dá)到redo log循環(huán)使用的目的舅桩。會調(diào)用write thread線程處理酱虎。
show variables like '%innodb_page_cleaners%';
Master Thread
Master thread是InnoDB的主線程,負(fù)責(zé)調(diào)度其他各線程擂涛,優(yōu)先級最高读串。作用是將緩沖池中的數(shù)據(jù)異步刷新到磁盤 ,保證數(shù)據(jù)的一致性撒妈。
這些工作包含:臟頁的刷新(page cleaner thread)恢暖、undo頁回收(purge thread)、redo日志刷新(log thread)狰右、合并寫緩沖等杰捂。在其內(nèi)部有兩個主去處理,分別是每隔1秒和10秒處理棋蚌。
每1秒的操作:
刷新日志緩沖區(qū)嫁佳,刷到磁盤
合并寫緩沖區(qū)數(shù)據(jù)峭弟,根據(jù)IO讀寫壓力來決定是否操作
刷新臟頁數(shù)據(jù)到磁盤,根據(jù)臟頁比例達(dá)到75%才操作(innodb_max_dirty_pages_pct脱拼,innodb_io_capacity)
每10秒的操作:
刷新臟頁數(shù)據(jù)到磁盤
合并寫緩沖區(qū)數(shù)據(jù)
刷新日志緩沖區(qū)
刪除無用的undo頁
數(shù)據(jù)文件
InnoDB數(shù)據(jù)文件存儲結(jié)構(gòu)
分為一個ibd數(shù)據(jù)文件-->Segment(段)-->Extent(區(qū))-->Page(頁)-->Row(行)
1. Tablesapce(表空間)
表空間用于存儲多個ibd數(shù)據(jù)文件,用于存儲表的記錄和索引坷备。一個文件包含多個段熄浓。
2. Segment(段)
段用于管理多個Extent,分為數(shù)據(jù)段(Leaf node segment)省撑、索引段(Non-leaf node segment)赌蔑、回滾段(Rollback segment)。一個表至少會有兩個segment竟秫,一個管理數(shù)據(jù)娃惯,一個管理索引。每多創(chuàng)建一個索引肥败,會多兩個segment趾浅。
3. Extent(區(qū))
一個區(qū)固定包含64個連續(xù)的頁,大小為1M馒稍。當(dāng)表空間不足皿哨,需要分配新的頁資源,不會一頁一頁分纽谒,直接分配一個區(qū)证膨。
4 Page(頁)
頁用于存儲多個Row行記錄,大小為16K鼓黔。包含很多種頁類型央勒,比如數(shù)據(jù)頁,undo頁澳化,系統(tǒng)頁崔步,事務(wù)數(shù)據(jù)頁,大的BLOB對象頁缎谷。
Page是文件最基本的單位刷晋,無論何種類型的page,都是由page header慎陵,page trailer和page body組成眼虱。如下圖所示:
5. Row(行)
行包含了記錄的字段值,事務(wù)ID(Trx id)席纽、滾動指針(Roll pointer)捏悬、字段指針(Fieldpointers)等信息。
InnoDB文件存儲格式
我們通過show table satus \G;命令润梯,查看InnoDB的文件狀態(tài)(示例的數(shù)據(jù)庫employee表):
一般情況下过牙,如果row_format為REDUNDANT甥厦、COMPACT,文件格式為Antelope寇钉;如果row_format為DYNAMIC和COMPRESSED刀疙,文件格式為Barracuda。
通過 information_schema 查看指定表的文件格式
select * from information_schema.innodb_sys_tables;
File文件格式(File-Format)
在早期的InnoDB版本中扫倡,文件格式只有一種谦秧,隨著InnoDB引擎的發(fā)展,出現(xiàn)了新文件格式撵溃,用于
支持新的功能疚鲤。目前InnoDB只支持兩種文件格式:Antelope 和 Barracuda。
Antelope: 先前未命名的缘挑,最原始的InnoDB文件格式集歇,它支持兩種行格式:COMPACT和REDUNDANT,MySQL 5.6及其以前版本默認(rèn)格式為Antelope语淘。
Barracuda: 新的文件格式诲宇。它支持InnoDB的所有行格式,包括新的行格式:COMPRESSED和 DYNAMIC惶翻。
通過innodb_file_format 配置參數(shù)可以設(shè)置InnoDB文件格式焕窝,之前默認(rèn)值為Antelope,5.7版本開始改為Barracuda维贺。
Row行格式(Row_format)
表的行格式?jīng)Q定了它的行是如何物理存儲的它掂,這反過來又會影響查詢和DML操作的性能。如果在單個page頁中容納更多行溯泣,查詢和索引查找可以更快地工作虐秋,緩沖池中所需的內(nèi)存更少,寫入更新時(shí)所需的I/O更少垃沦。
InnoDB存儲引擎支持四種行格式:REDUNDANT客给、COMPACT、DYNAMIC和COMPRESSED肢簿。
DYNAMIC和COMPRESSED新格式引入的功能有:數(shù)據(jù)壓縮靶剑、增強(qiáng)型長列數(shù)據(jù)的頁外存儲和大索引前綴。
每個表的數(shù)據(jù)分成若干頁來存儲池充,每個頁中采用B樹結(jié)構(gòu)存儲桩引。
如果某些字段信息過長,無法存儲在B樹節(jié)點(diǎn)中收夸,這時(shí)候會被單獨(dú)分配空間坑匠,此時(shí)被稱為溢出頁,該字段被稱為頁外列卧惜。
1. REDUNDANT 行格式
使用REDUNDANT行格式厘灼,表會將變長列值的前768字節(jié)存儲在B樹節(jié)點(diǎn)的索引記錄中夹纫,其余的存儲在溢出頁上。對于大于等于786字節(jié)的固定長度字段InnoDB會轉(zhuǎn)換為變長字段设凹,以便能夠在頁外存儲舰讹。
2. COMPACT 行格式
與REDUNDANT行格式相比,COMPACT行格式減少了約20%的行存儲空間闪朱,但代價(jià)是增加了某些操作的CPU使用量月匣。如果系統(tǒng)負(fù)載是受緩存命中率和磁盤速度限制,那么COMPACT格式可能更快监透。如果系統(tǒng)負(fù)載受到CPU速度的限制,那么COMPACT格式可能會慢一些航唆。
3. DYNAMIC 行格式
使用DYNAMIC行格式胀蛮,InnoDB會將表中長可變長度的列值完全存儲在頁外,而索引記錄只包含指向溢出頁的20字節(jié)指針糯钙。大于或等于768字節(jié)的固定長度字段編碼為可變長度字段粪狼。
DYNAMIC行格式支持大索引前綴,最多可以為3072字節(jié)任岸,可通過innodb_large_prefix參數(shù)控制再榄。
4. COMPRESSED 行格式
COMPRESSED行格式提供與DYNAMIC行格式相同的存儲特性和功能,但增加了對表和索引數(shù)據(jù)壓縮的支持享潜。
在創(chuàng)建表和索引時(shí)困鸥,文件格式都被用于每個InnoDB表數(shù)據(jù)文件(其名稱與*.ibd匹配)。
修改文件格式的方法是重新創(chuàng)建表及其索引剑按,最簡單方法是對要修改的每個表使用以下命令:ALTER TABLE 表名 ROW_FORMAT=格式類型;