InnoDB邏輯存儲結(jié)構(gòu)

InnoDB體系架構(gòu)

InnoDB存儲引擎體系架構(gòu)

上圖簡單顯示了InnoDB存儲引擎的體系架構(gòu)圖中可見,InnoDB存儲引擎有多個內(nèi)存塊,可以認(rèn)為這些內(nèi)存塊組成了一個大的內(nèi)存池,負(fù)責(zé)如下工作:

維護(hù)所有進(jìn)程/線程需要訪問的多個內(nèi)部數(shù)據(jù)結(jié)構(gòu)。

緩存磁盤上的數(shù)據(jù),方便快速地讀取,同時在對磁盤文件的數(shù)據(jù)修改之前在這里緩存。

重做日志(redo log)緩沖

......

后臺線程的主要作用是負(fù)責(zé)刷新內(nèi)存池中的數(shù)據(jù),保證緩沖池中的內(nèi)存緩存是最近的數(shù)據(jù)。此外將已修改的數(shù)據(jù)文件刷新到磁盤文件,同時保證在數(shù)據(jù)庫發(fā)生異常的情況下InnoDB能恢復(fù)到正常運(yùn)行狀態(tài)。

后臺線程

InnoDB存儲引擎是多線程的模型,因此其后臺有多個不同的后臺線程,負(fù)責(zé)處理不同的任務(wù)。

1.Master Thread

Master Thread是一個非常核心的后臺線程,主要負(fù)責(zé)將緩沖池中的數(shù)據(jù)異步刷新到磁盤,保證數(shù)據(jù)的一致性,包括臟頁的刷新,合并插入緩沖、UNDO頁的回收等。

2.IO Thread

在InnoDB中大量使用了AIO來處理寫IO請求,這樣可以極大提高數(shù)據(jù)庫的性能。而IO Thread的工作主要負(fù)責(zé)這些IO請求的回調(diào)處理。

可通過命令SHOW ENGINE INNODB STATUS來觀察InnoDB中的IO Thread。

3.Purge Thread

事務(wù)被提交后,其所使用的undo log可能不再需要,因此需要PurgeThread來回收已經(jīng)使用并分配的undo頁。用戶可以在MySQL數(shù)據(jù)庫的配置文件中添加如下命令來啟動獨(dú)立的Purge Thread:

innodb_purge_threads=1

4.Page Cleaner Thread

InnoDB 1.2版本中引入。其作用是將之前版本中臟頁的刷新操作都放到單獨(dú)的線程中來完成,目的是減輕原Master Thread的工作及對于用戶查詢線程的阻塞,進(jìn)一步提高InnoDB存儲引擎的性能。

內(nèi)存

1.緩沖池(Database Buffer Pool)

InnoDB存儲引擎是基于磁盤存儲的,并將其中的記錄按照頁的方式進(jìn)行管理。在數(shù)據(jù)庫管理系統(tǒng)中,由于CPU速度與磁盤速度之間的鴻溝,基于磁盤的數(shù)據(jù)庫系統(tǒng)通常使用緩沖池技術(shù)來提高數(shù)據(jù)庫的整體性能。

緩存池簡單來說就是一塊內(nèi)存區(qū)域,通過內(nèi)存的速度來彌補(bǔ)磁盤速度較慢對數(shù)據(jù)庫性能的影響。在數(shù)據(jù)庫中進(jìn)行讀取頁的操作,首先將從磁盤讀到的頁存放在緩沖池中,這個過程稱為“FIX”在緩沖池中。下一次再讀相同的頁時,首先判斷該頁是否在緩沖池中。若在緩沖池中,稱為頁在緩沖池中被命中,直接讀取該頁。否則,讀取磁盤上的頁。

對于數(shù)據(jù)庫中頁的修改操作,則首先修改在緩沖池中的頁,然后再以一定的頻率刷新到磁盤上(并不是每次頁發(fā)生更新時刷新,而是通過一種稱為CheckPoint的機(jī)制刷新回磁盤)。

綜上所述,緩沖池的大小直接影響數(shù)據(jù)庫的整體性能。對于InnoDB而言,通過參數(shù)innodb_buffer_pool_size來設(shè)置。

緩沖池中緩存的數(shù)據(jù)頁類型有:索引頁、數(shù)據(jù)頁、undo頁、插入緩沖、自適應(yīng)哈希索引、InnoDB存儲的鎖信息、數(shù)據(jù)字典信息等。

InnoDB內(nèi)存數(shù)據(jù)對象

InnoDB允許有多個緩沖池實(shí)例。每個頁根據(jù)哈希值平均分配到不同的緩沖池實(shí)例中,好處是減少數(shù)據(jù)庫內(nèi)部的資源競爭,增加數(shù)據(jù)庫的并發(fā)能力。

2.LRU List、Free List和Flush List

通常來說,數(shù)據(jù)庫中的緩沖池是通過LRU(Latest Recent Used)算法來進(jìn)行管理的。即最頻繁使用的頁在LRU列表的前端,而最少使用的頁在LRU列表的尾端。當(dāng)緩沖池不能存放新讀取到的頁時,將首先釋放LRU列表中尾端的頁。

在InnoDB中,對傳統(tǒng)的LRU算法做了一些優(yōu)化,添加了midpoint位置。新讀取到的頁不是直接放到LRU列表的首部,而是放到midpoint位置。在默認(rèn)配置下,該位置在LRU列表長度的5/8處。通過參數(shù)innodb_old_blocks_pct控制。

在InnoDB中,把midpoint之后的列表稱為old列表,之前的列表稱為new列表。InnoDB引入另一個參數(shù)innodb_old_blocks_time來管理列表,用于表示頁讀取到mid位置后需要等待多久才會被加入到LRU列表的熱端。

LRU列表用來管理已經(jīng)讀取的頁,但當(dāng)數(shù)據(jù)庫剛啟動時,LRU列表是空的,即沒有任何的頁。這時頁先存放在Free列表中。當(dāng)需要從緩沖池中分頁時,首先從Free列表中查找是否有可用的空閑頁若有則將該頁從Free列表中刪除,放入到LRU列表中。否則,根據(jù)LRU算法,淘汰LRU列表末尾的頁,將該內(nèi)存空間分配給新的頁。當(dāng)頁從LRU列表的old部分加入到new部分時,稱此時的操作為page made young,而因?yàn)閕nnodb_old_blocks_time的設(shè)置而導(dǎo)致頁沒有從old部分移動到new部分的操作稱為page not made young。

可以通過命令SHOW ENGINE INNODB STATUS來查看LRU列表及FREE列表的使用情況和運(yùn)行狀態(tài)。

其中Buffer pool size表示緩沖池中頁的數(shù)量。

Free Buffers表示當(dāng)前Free列表中頁的數(shù)量。

Database pages表示LRU列表中頁的數(shù)量。

pages made young顯示了LRU列表中頁移動到前端的次數(shù)。

Buffer pool hit rate表示緩沖池命中率,通常該值不應(yīng)該小于95%,否則需要觀察是否由于全表掃描引起的LRU列表被污染。

Modified db pages 表示臟頁的數(shù)量。

在LRU列表中的頁被修改后,稱該頁為臟頁,即緩沖池中的頁和磁盤上的頁的數(shù)據(jù)產(chǎn)生了不一致。這時數(shù)據(jù)庫會通過CHECKPOINT機(jī)制將臟頁刷新回磁盤,而Flush列表中的頁即為臟頁列表。需要注意的是,臟頁既存在于LRU列表中,也存在于Flush列表中,二者互不影響。

3.重做日志緩沖

InnoDB存儲引擎首先將重做日志信息先放入到這個緩沖區(qū),然后按一定的頻率將其刷新到重做日志文件。重做日志緩沖一般不需要設(shè)置得很大,因?yàn)橐话忝棵刖蜁⑿乱淮巍?/p>

重做日志在下列三種情況下會將緩沖內(nèi)容刷新到外部磁盤的重做日志文件中:

Master Thread每一秒將重做日志緩沖刷新到重做日志文件。

每個事務(wù)提交時會將重做日志緩沖刷新到重做日志文件。

當(dāng)重做日志緩沖池剩余空間小于1/2時,重做日志緩沖刷新到重做日志文件。

4.額外的內(nèi)存池

在InnoDB存儲引擎中,對內(nèi)存的管理是通過一種稱為內(nèi)存堆的方式進(jìn)行的。在對一些數(shù)據(jù)結(jié)構(gòu)本身的內(nèi)存進(jìn)行分配時,需要從額外的內(nèi)存池中進(jìn)行申請,當(dāng)該區(qū)域的內(nèi)存不夠時會從緩沖池中進(jìn)行申請。例如,分配了緩沖池,但是每個緩沖池中的幀緩沖還有對應(yīng)的緩沖控制對象,這些對象記錄了一些諸如LRU、鎖、等待等信息,而這個對象的內(nèi)存需要從額外內(nèi)存池中申請。因此申請了很大的InnoDB緩沖池時,也需要考慮相應(yīng)增加這個值。

Checkpoint技術(shù)

為了避免每次頁發(fā)送變化就將其刷新至磁盤,采用了Checkpoint技術(shù)統(tǒng)一對刷新進(jìn)行管理。但是為了防止刷新時發(fā)生宕機(jī)引起數(shù)據(jù)丟失的情況,數(shù)據(jù)庫系統(tǒng)普遍采用了Write Ahead Log策略,即當(dāng)前事務(wù)提交時,先寫重做日志,再修改頁。這樣宕機(jī)導(dǎo)致數(shù)據(jù)丟失時,可以通過重做日志來完成數(shù)據(jù)的恢復(fù)。

Checkpoint技術(shù)是為了解決以下幾個問題:

縮短數(shù)據(jù)庫的恢復(fù)時間

緩沖池不夠用時,將臟頁刷新到磁盤

重做日志不可用時,刷新臟頁

當(dāng)數(shù)據(jù)庫發(fā)生宕機(jī)時,數(shù)據(jù)庫不需要重做所有的日志,因?yàn)镃heckpoint之前的頁都已經(jīng)刷新回磁盤。只需要對Checkpoint后的重做日志進(jìn)行恢復(fù)。這樣就大大縮短了恢復(fù)時間。

當(dāng)緩沖池不夠用時,根據(jù)LRU算法會溢出最近最少使用的頁,若此頁為臟頁,那么需要強(qiáng)制執(zhí)行Checkpoint,將臟頁刷回磁盤。

重做日志出現(xiàn)不可用的情況是因?yàn)楫?dāng)前事務(wù)數(shù)據(jù)庫系統(tǒng)對重做日志的設(shè)計都是循環(huán)使用的,并不是無限增大的。因此為了保證重做日志正常的循環(huán)使用,需要將臟頁及時刷新到磁盤。

InnoDB存儲引擎內(nèi)部,有兩種CheckPoint,分別為:Sharp Checkpoint、Fuzzy Checkpoint。

Sharp Checkpoint發(fā)生在數(shù)據(jù)庫關(guān)閉時將所有的臟頁都刷新回磁盤,這時默認(rèn)的工作方式。

但是數(shù)據(jù)庫運(yùn)行并不會將所有的臟頁同時刷新回磁盤,在InnoDB內(nèi)部使用Fuzzy Checkpoint進(jìn)行頁的刷新,即只刷新一部分臟頁,而不是刷新所有的臟頁回磁盤。

以下幾種情況會發(fā)生Fuzzy Checkpoint:

Master Thread Checkpoint

每秒或者每十秒從緩沖池的臟頁列表中刷新一定比例的頁回磁盤。這個過程是異步的,即此時InnoDB存儲引擎可以進(jìn)行其他的操作,用戶查詢線程不會阻塞。

FLUSH_LRU_LIST Checkpoint

倘若LRU列表中沒有100個空閑頁可用,引擎會將LRU列表尾端的頁移除。如果這些頁中有臟頁,那么需要進(jìn)行Checkpoint。MySQL5.6版本后,這個檢查放在了一個單獨(dú)的Page Cleaner線程中,并且用戶可以通過參數(shù)innodb_lru_scan_depth控制列表中可用頁的數(shù)量。

Async/Synv Flush Checkpoing

重做日志文件不可用的情況下,這時需要強(qiáng)制將一些頁刷新回磁盤。MySQL5.6版本后,這部分刷新操作同樣放入到單獨(dú)的Page Cleaner Thread,故不會阻塞用戶查詢進(jìn)程。

Dirty Page too much Checkpoint

臟頁的數(shù)量太多,導(dǎo)致InnoDB存儲引擎強(qiáng)制進(jìn)行Checkpoint,可以通過參數(shù)innodb_max_dirty_pages_pct控制。

Master Thread工作方式

InnoDB 1.0版本之前,Master Thread具有最高的線程優(yōu)先級別。其內(nèi)部由多個循環(huán)組成:主循環(huán)、后臺循環(huán)、刷新循環(huán)、暫停循環(huán)。Master Thread會根據(jù)數(shù)據(jù)庫運(yùn)行的狀態(tài)在其中進(jìn)行切換。

主循環(huán)大概每秒一次或每10秒一次,每次的操作包括:

日志緩沖刷新到磁盤,即使這個事務(wù)還沒有提交(總是);

合并插入緩沖(可能);

至多刷新100個InnoDB的緩沖池中的臟頁到磁盤(可能);

如果當(dāng)前沒有用戶活動,切換到backgroud loop(可能)。

當(dāng)前沒有用戶活動或者數(shù)據(jù)庫關(guān)閉時,會切換到后臺循環(huán)。后臺循環(huán)會執(zhí)行以下操作:

刪除無用的Undo頁(總是);

合并20個插入緩沖(總是);

跳回到主循環(huán)(總是);

不斷刷新100個頁直到符合條件(可能)。

若刷新循環(huán)中也沒什么事情可以做了,InnoDB存儲引擎會切換到暫停循環(huán),將Master Thread掛起。

InnoDB 1.2.x之前版本的Master Thread

在了解了1.0.x版本之前的Master Thread的具體實(shí)現(xiàn)過程后,細(xì)心的讀者會發(fā)現(xiàn)InnoDB存儲引擎對于IO其實(shí)是有限制的,在緩沖池向磁盤刷新時其實(shí)都做了一定的硬編碼(hard coding)。在磁盤技術(shù)飛速發(fā)展的今天,當(dāng)固態(tài)磁盤(SSD)出現(xiàn)時,這種規(guī)定在很大程度上限制了InnoDB存儲引擎對磁盤IO的性能,尤其是寫入性能。

從前面的偽代碼來看,無論何時,InnoDB存儲引擎最大只會刷新100個臟頁到磁盤,合并20個插入緩沖。如果是在寫入密集的應(yīng)用程序中,每秒可能會產(chǎn)生大于100個的臟頁,如果是產(chǎn)生大于20個插入緩沖的情況,Master Thread似乎會“忙不過來”,或者說它總是做得很慢。即使磁盤能在1秒內(nèi)處理多于100個頁的寫入和20個插入緩沖的合并,但是由于hard coding,Master Thread也只會選擇刷新100個臟頁和合并20個插入緩沖。同時,當(dāng)發(fā)生宕機(jī)需要恢復(fù)時,由于很多數(shù)據(jù)還沒有刷新回磁盤,會導(dǎo)致恢復(fù)的時間可能需要很久,尤其是對于insert buffer來說。

InnoDB Plugin(從InnoDB1.0.x版本開始)提供了參數(shù)innodb_io_capacity,用來表示磁盤IO的吞吐量,默認(rèn)值為200。對于刷新到磁盤頁的數(shù)量,會按照innodb_io_capacity的百分比來進(jìn)行控制。規(guī)則如下:

在合并插入緩沖時,合并插入緩沖的數(shù)量為innodb_io_capacity值的5%;

在從緩沖區(qū)刷新臟頁時,刷新臟頁的數(shù)量為innodb_io_capacity。

若用戶使用了SSD類的磁盤,或者將幾塊磁盤做了RAID,當(dāng)存儲設(shè)備擁有更高的IO速度時,完全可以將innodb_io_capacity的值調(diào)得再高點(diǎn),直到符合磁盤IO的吞吐量為止。

另一個問題是,參數(shù)innodb_max_dirty_pages_pct默認(rèn)值的問題,在InnoDB 1.0.x版本之前,該值的默認(rèn)為90,意味著臟頁占緩沖池的90%。但是該值“太大”了,因?yàn)镮nnoDB存儲引擎在每秒刷新緩沖池和flush loop時會判斷這個值,如果該值大于innodb_max_dirty_pages_pct,才刷新100個臟頁,如果有很大的內(nèi)存,或者服務(wù)器的壓力很大,這時刷新臟頁的速度反而會降低。同樣,在恢復(fù)階段可能需要更多的時間。

InnoDB 1.0.x版本帶來的另一個參數(shù)是innodb_adaptive_flushing(自適應(yīng)地刷新),該值影響每秒刷新臟頁的數(shù)量。原來的刷新規(guī)則是:臟頁在緩沖池所占的比例小于innodb_max_dirty_pages_pct時,不刷新臟頁;大于innodb_max_dirty_pages_pct時,刷新100個臟頁。隨著innodb_adaptive_flushing參數(shù)的引入,InnoDB存儲引擎會通過一個名為buf_flush_get_desired_flush_rate的函數(shù)來判斷需要刷新臟頁最合適的數(shù)量。粗略地翻閱源代碼后發(fā)現(xiàn)buf_flush_get_desired_flush_rate通過判斷產(chǎn)生重做日志(redo log)的速度來決定最合適的刷新臟頁數(shù)量。因此,當(dāng)臟頁的比例小于innodb_max_dirty_pages_pct時,也會刷新一定量的臟頁。

InnoDB 1.2.x版本的Master Thread

在InnoDB 1.2.x版本中再次對Master Thread進(jìn)行了優(yōu)化,由此也可以看出Master Thread對性能所起到的關(guān)鍵作用。在InnoDB 1.2.x版本中,Master Thread的偽代碼如下:

if InnoDB is idle

srv_master_do_idle_tasks();

else

srv_master_do_active_tasks();

其中srv_master_do_idle_tasks()就是之前版本中每10秒的操作,srv_master_do_active_tasks()處理的是之前每秒中的操作。同時對于刷新臟頁的操作,從Master Thread線程分離到一個單獨(dú)的Page Cleaner Thread,從而減輕了Master Thread的工作,同時進(jìn)一步提高了系統(tǒng)的并發(fā)性。

InnoDB關(guān)鍵特性

InnoDB存儲引擎的關(guān)鍵特性包括:

插入緩沖(Insert Buffer)

兩次寫(Double Write)

自適應(yīng)哈希索引(Adaptive Hash Index)

異步IO(Async IO)

刷新鄰接頁(Flush Neighbor Page)

上述這些特性為InnoDB存儲引擎帶來更好的性能以及更高的可靠性。

插入緩沖

1.Insert Buffer

這個名字可能會讓人認(rèn)為插入緩沖是緩沖池中的一個組成部分。其實(shí)不然,InnoDB緩沖池中有Insert Buffer信息固然不錯,但是Insert Buffer和數(shù)據(jù)頁一樣,也是物理頁的一個組成部分。

在InnoDB存儲引擎中,主鍵是行唯一的標(biāo)識符。通常應(yīng)用程序中行記錄的插入順序是按照主鍵遞增的順序進(jìn)行插入的。因此,插入聚集索引(Primary Key)一般是順序的,不需要磁盤的隨機(jī)讀取。比如按下列SQL定義表:

CREATE TABLE t (

a INT AUTO_INCREMENT,

b VARCHAR(30),

PRIMARY KEY(a));

其中a列是自增長的,若對a列插入NULL值,則由于其具有AUTO_INCREMENT屬性,其值會自動增長。同時頁中的行記錄按a的值進(jìn)行順序存放。在一般情況下,不需要隨機(jī)讀取另一個頁中的記錄。因此,對于這類情況下的插入操作,速度是非??斓?。

注意

并不是所有的主鍵插入都是順序的。若主鍵類是UUID這樣的類,那么插入和輔助索引一樣,同樣是隨機(jī)的。即使主鍵是自增類型,但是插入的是指定的值,而不是NULL值,那么同樣可能導(dǎo)致插入并非連續(xù)的情況。

但是不可能每張表上只有一個聚集索引,更多情況下,一張表上有多個非聚集的輔助索引(secondary index)。比如,用戶需要按照b這個字段進(jìn)行查找,并且b這個字段不是唯一的。

在這樣的情況下產(chǎn)生了一個非聚集的且不是唯一的索引。在進(jìn)行插入操作時,數(shù)據(jù)頁的存放還是按主鍵a進(jìn)行順序存放的,但是對于非聚集索引葉子節(jié)點(diǎn)的插入不再是順序的了,這時就需要離散地訪問非聚集索引頁,由于隨機(jī)讀取的存在而導(dǎo)致了插入操作性能下降。當(dāng)然這并不是這個b字段上索引的錯誤,而是因?yàn)锽+樹的特性決定了非聚集索引插入的離散性。

需要注意的是,在某些情況下,輔助索引的插入依然是順序的,或者說是比較順序的,比如用戶購買表中的時間字段。在通常情況下,用戶購買時間是一個輔助索引,用來根據(jù)時間條件進(jìn)行查詢。但是在插入時卻是根據(jù)時間的遞增而插入的,因此插入也是“較為”順序的。

InnoDB存儲引擎開創(chuàng)性地設(shè)計了Insert Buffer,對于非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩沖池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer對象中??此品蔷奂乃饕呀?jīng)插到葉子節(jié)點(diǎn),而實(shí)際并沒有,只是存放在另一個位置。然后再以一定的頻率和情況進(jìn)行Insert Buffer和輔助索引頁子節(jié)點(diǎn)的merge(合并)操作,這時通常能將多個插入合并到一個操作中(因?yàn)樵谝粋€索引頁中),這就大大提高了對于非聚集索引插入的性能。

然而Insert Buffer的使用需要同時滿足以下兩個條件:

索引是輔助索引(secondary index);

索引不是唯一(unique)的。

當(dāng)滿足以上兩個條件時,InnoDB存儲引擎會使用Insert Buffer,這樣就能提高插入操作的性能了。不過考慮這樣一種情況:應(yīng)用程序進(jìn)行大量的插入操作,這些都涉及了不唯一的非聚集索引,也就是使用了Insert Buffer。若此時MySQL發(fā)生了宕機(jī),這時勢必有大量的Insert Buffer并沒有合并到實(shí)際的非聚集索引中去。因此這時恢復(fù)可能需要很長的時間,在極端情況下甚至需要幾個小時。

輔助索引不能是唯一的,因?yàn)樵诓迦刖彌_時,數(shù)據(jù)庫并不去查找索引頁來判斷插入的記錄的唯一性。如果去查找肯定又會有離散讀取的情況發(fā)生,從而導(dǎo)致Insert Buffer失去了意義。

用戶可以通過命令SHOW ENGINE INNODB STATUS來查看插入緩沖的信息。

正如前面所說的,目前Insert Buffer存在一個問題是:在寫密集的情況下,插入緩沖會占用過多的緩沖池內(nèi)存(innodb_buffer_pool),默認(rèn)最大可以占用到1/2的緩沖池內(nèi)存。

這對于其他的操作可能會帶來一定的影響,修改IBUF_POOL_SIZE_PER_MAX_SIZE就可以對插入緩沖的大小進(jìn)行控制。比如將IBUF_POOL_SIZE_PER_MAX_SIZE改為3,則最大只能使用1/3的緩沖池內(nèi)存。

2.Change Buffer

InnoDB從1.0.x版本開始引入了Change Buffer,可將其視為Insert Buffer的升級。從這個版本開始,InnoDB存儲引擎可以對DML操作——INSERT、DELETE、UPDATE都進(jìn)行緩沖,他們分別是:Insert Buffer、Delete Buffer、Purge buffer。

當(dāng)然和之前Insert Buffer一樣,Change Buffer適用的對象依然是非唯一的輔助索引。

對一條記錄進(jìn)行UPDATE操作可能分為兩個過程:

將記錄標(biāo)記為已刪除;

真正將記錄刪除。

因此Delete Buffer對應(yīng)UPDATE操作的第一個過程,即將記錄標(biāo)記為刪除。Purge Buffer對應(yīng)UPDATE操作的第二個過程,即將記錄真正的刪除。同時,InnoDB存儲引擎提供了參數(shù)innodb_change_buffering,用來開啟各種Buffer的選項(xiàng)。該參數(shù)可選的值為:inserts、deletes、purges、changes、all、none。inserts、deletes、purges就是前面討論過的三種情況。changes表示啟用inserts和deletes,all表示啟用所有,none表示都不啟用。該參數(shù)默認(rèn)值為all。

從InnoDB 1.2.x版本開始,可以通過參數(shù)innodb_change_buffer_max_size來控制Change Buffer最大使用內(nèi)存的數(shù)量。

兩次寫

如果說Insert Buffer帶給InnoDB存儲引擎的是性能上的提升,那么doublewrite(兩次寫)帶給InnoDB存儲引擎的是數(shù)據(jù)頁的可靠性。

當(dāng)發(fā)生數(shù)據(jù)庫宕機(jī)時,可能InnoDB存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,比如16KB的頁,只寫了前4KB,之后就發(fā)生了宕機(jī),這種情況被稱為部分寫失效(partial page write)。在InnoDB存儲引擎未使用doublewrite技術(shù)前,曾經(jīng)出現(xiàn)過因?yàn)椴糠謱懯Ф鴮?dǎo)致數(shù)據(jù)丟失的情況。

有經(jīng)驗(yàn)的DBA也許會想,如果發(fā)生寫失效,可以通過重做日志進(jìn)行恢復(fù)。這是一個辦法。但是必須清楚地認(rèn)識到,重做日志中記錄的是對頁的物理操作,如偏移量800,寫'aaaa'記錄。如果這個頁本身已經(jīng)發(fā)生了損壞,再對其進(jìn)行重做是沒有意義的。這就是說,在應(yīng)用(apply)重做日志前,用戶需要一個頁的副本,當(dāng)寫入失效發(fā)生時,先通過頁的副本來還原該頁,再進(jìn)行重做,這就是doublewrite。在InnoDB存儲引擎中doublewrite的體系架構(gòu)如下圖所示。

innodb引擎double write架構(gòu)

doublewrite由兩部分組成,一部分是內(nèi)存中的doublewrite buffer,大小為2MB,另一部分是物理磁盤上共享表空間中連續(xù)的128個頁,即2個區(qū)(extent),大小同樣為2MB。在對緩沖池的臟頁進(jìn)行刷新時,并不直接寫磁盤,而是會通過memcpy函數(shù)將臟頁先復(fù)制到內(nèi)存中的doublewrite buffer,之后通過doublewrite buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁盤上,然后馬上調(diào)用fsync函數(shù),同步磁盤,避免緩沖寫帶來的問題。在這個過程中,因?yàn)閐oublewrite頁是連續(xù)的,因此這個過程是順序?qū)懙?,開銷并不是很大。在完成doublewrite頁的寫入后,再將doublewrite buffer中的頁寫入各個表空間文件中,此時的寫入則是離散的。

若查看MySQL官方手冊,會發(fā)現(xiàn)在命令SHOW GLOBAL STATUS中Innodb_buffer_pool_pages_flushed變量表示當(dāng)前從緩沖池中刷新到磁盤頁的數(shù)量。根據(jù)之前的介紹,用戶應(yīng)該了解到,在默認(rèn)情況下所有頁的刷新首先都需要放入到doublewrite中,因此該變量應(yīng)該和Innodb_dblwr_pages_written一致。然而在MySQL 5.5.24版本之前,Innodb_buffer_pool_pages_flushed總是為Innodb_dblwr_pages_written的2倍,而此Bug直到MySQL5.5.24才被修復(fù)。因此用戶若需要統(tǒng)計數(shù)據(jù)庫在生產(chǎn)環(huán)境中寫入的量,最安全的方法還是根據(jù)Innodb_dblwr_pages_written來進(jìn)行統(tǒng)計,這在所有版本的MySQL數(shù)據(jù)庫中都是正確的。

參數(shù)skip_innodb_doublewrite可以禁止使用doublewrite功能,這時可能會發(fā)生前面提及的寫失效問題。不過如果用戶有多個從服務(wù)器(slave server),需要提供較快的性能(如在slaves erver上做的是RAID0),也許啟用這個參數(shù)是一個辦法。不過對于需要提供數(shù)據(jù)高可靠性的主服務(wù)器(master server),任何時候用戶都應(yīng)確保開啟doublewrite功能。

自適應(yīng)哈希索引

哈希(hash)是一種非??斓牟檎曳椒ǎ谝话闱闆r下這種查找的時間復(fù)雜度為O(1),即一般僅需要一次查找就能定位數(shù)據(jù)。而B+樹的查找次數(shù),取決于B+樹的高度,在生產(chǎn)環(huán)境中,B+樹的高度一般為3~4層,故需要3~4次的查詢。

InnoDB存儲引擎會監(jiān)控對表上各索引頁的查詢。如果觀察到建立哈希索引可以帶來速度提升,則建立哈希索引,稱之為自適應(yīng)哈希索引(Adaptive Hash Index,AHI)。AHI是通過緩沖池的B+樹頁構(gòu)造而來,因此建立的速度很快,而且不需要對整張表構(gòu)建哈希索引。InnoDB存儲引擎會自動根據(jù)訪問的頻率和模式來自動地為某些熱點(diǎn)頁建立哈希索引。

AHI有一個要求,即對這個頁的連續(xù)訪問模式必須是一樣的。例如對于(a,b)這樣的聯(lián)合索引頁,其訪問模式可以是以下情況:

WHERE a=xxx

WHERE a=xxx and b=xxx

訪問模式一樣指的是查詢的條件一樣,若交替進(jìn)行上述兩種查詢,那么InonDB存儲引擎不會對該頁構(gòu)造AHI。此外AHI還有如下的要求:

以該模式訪問了100次

頁通過該模式訪問了N次,其中N=頁中記錄*1/16

根據(jù)InnoDB存儲引擎官方的文檔顯示,啟用AHI后,讀取和寫入速度可以提高2倍,輔助索引的連接操作性能可以提高5倍。毫無疑問,AHI是非常好的優(yōu)化模式,其設(shè)計思想是數(shù)據(jù)庫自優(yōu)化的(self-tuning),即無需DBA對數(shù)據(jù)庫進(jìn)行人為調(diào)整。

值得注意的是,哈希索引只能用來搜索等值的查詢,如SELECT*FROM table WHERE index_col='xxx'。而對于其他查找類型,如范圍查找,是不能使用哈希索引的。

由于AHI是由InnoDB存儲引擎控制的,因此這里的信息只供用戶參考。不過用戶可以通過觀察SHOW ENGINE INNODB STATUS的結(jié)果及參數(shù)innodb_adaptive_hash_index來考慮是禁用或啟動此特性,默認(rèn)AHI為開啟狀態(tài)。

異步IO

為了提高磁盤操作性能,當(dāng)前的數(shù)據(jù)庫系統(tǒng)都采用異步IO(Asynchronous IO,AIO)的方式來處理磁盤操作。InnoDB存儲引擎亦是如此。

與AIO對應(yīng)的是Sync IO,即每進(jìn)行一次IO操作,需要等待此次操作結(jié)束才能繼續(xù)接下來的操作。但是如果用戶發(fā)出的是一條索引掃描的查詢,那么這條SQL查詢語句可能需要掃描多個索引頁,也就是需要進(jìn)行多次的IO操作。在每掃描一個頁并等待其完成后再進(jìn)行下一次的掃描,這是沒有必要的。用戶可以在發(fā)出一個IO請求后立即再發(fā)出另一個IO請求,當(dāng)全部IO請求發(fā)送完畢后,等待所有IO操作的完成,這就是AIO。

AIO的另一個優(yōu)勢是可以進(jìn)行IO Merge操作,也就是將多個IO合并為1個IO,這樣可以提高IOPS的性能。例如用戶需要訪問頁的(space,page_no)為:

(8,6)、(8,7),(8,8)

每個頁的大小為16KB,那么同步IO需要進(jìn)行3次IO操作。而AIO會判斷到這三個頁是連續(xù)的,因此AIO底層會發(fā)送一個IO請求,從(8,6)開始,讀取48KB的頁。

在InnoDB1.1.x之前,AIO的實(shí)現(xiàn)通過InnoDB存儲引擎中的代碼來模擬實(shí)現(xiàn)。而從InnoDB 1.1.x開始(InnoDB Plugin不支持),提供了內(nèi)核級別AIO的支持,稱為Native AIO。因此在編譯或者運(yùn)行該版本MySQL時,需要libaio庫的支持。

需要注意的是,Native AIO需要操作系統(tǒng)提供支持。Windows系統(tǒng)和Linux系統(tǒng)都提供Native AIO支持,而Mac OSX系統(tǒng)則未提供。因此在這些系統(tǒng)下,依舊只能使用原模擬的方式。

參數(shù)innodb_use_native_aio用來控制是否啟用Native AIO,在Linux操作系統(tǒng)下,默認(rèn)值為ON:

用戶可以通過開啟和關(guān)閉Native AIO功能來比較InnoDB性能的提升。官方的測試顯示,啟用Native AIO,恢復(fù)速度可以提高75%。

在InnoDB存儲引擎中,read ahead方式的讀取都是通過AIO完成,臟頁的刷新,即磁盤的寫入操作則全部由AIO完成。

刷新鄰接頁

InnoDB存儲引擎還提供了Flush Neighbor Page(刷新鄰接頁)的特性。其工作原理為:當(dāng)刷新一個臟頁時,InnoDB存儲引擎會檢測該頁所在區(qū)(extent)的所有頁,如果是臟頁,那么一起進(jìn)行刷新。這樣做的好處顯而易見,通過AIO可以將多個IO寫入操作合并為一個IO操作,故該工作機(jī)制在傳統(tǒng)機(jī)械磁盤下有著顯著的優(yōu)勢。但是需要考慮到下面兩個問題:

是不是可能將不怎么臟的頁進(jìn)行了寫入,而該頁之后又會很快變成臟頁?

固態(tài)硬盤有著較高的IOPS,是否還需要這個特性?

為此,InnoDB存儲引擎從1.2.x版本開始提供了參數(shù)innodb_flush_neighbors,用來控制是否啟用該特性。對于傳統(tǒng)機(jī)械硬盤建議啟用該特性,而對于固態(tài)硬盤有著超高IOPS性能的磁盤,則建議將該參數(shù)設(shè)置為0,即關(guān)閉此特性。

啟動、關(guān)閉與恢復(fù)

InnoDB是MySQL的存儲引擎之一,因此InnoDB存儲引擎的啟動和關(guān)閉,更準(zhǔn)確的是指在MySQL實(shí)例的啟動過程中對InnoDB存儲引擎的處理過程。

在關(guān)閉時,參數(shù)innodb_fast_shutdown影響著表的存儲引擎為InnoDB的行為。該參數(shù)可取值為0、1、2,默認(rèn)值為1。

0表示在MySQL數(shù)據(jù)庫關(guān)閉時,InnoDB需要完成所有的full purge和merge insert buffer,并且將所有的臟頁刷新回磁盤。這需要一些時間,有時甚至需要幾個小時來完成。如果在進(jìn)行InnoDB升級時,必須將這個參數(shù)調(diào)為0,然后再關(guān)閉數(shù)據(jù)庫。

1是參數(shù)innodb_fast_shutdown的默認(rèn)值,表示不需要完成上述的full purge和merge insert buffer操作,但是在緩沖池中的一些數(shù)據(jù)臟頁還是會刷新回磁盤。

2表示不完成full purge和merge insert buffer操作,也不將緩沖池中的數(shù)據(jù)臟頁寫回磁盤,而是將日志都寫入日志文件。這樣不會有任何事務(wù)的丟失,但是下次MySQL數(shù)據(jù)庫啟動時,會進(jìn)行恢復(fù)操作(recovery)。

當(dāng)正常關(guān)閉MySQL數(shù)據(jù)庫時,下次的啟動應(yīng)該會非?!罢!薄5侨绻麤]有正常地關(guān)閉數(shù)據(jù)庫,如用kill命令關(guān)閉數(shù)據(jù)庫,在MySQL數(shù)據(jù)庫運(yùn)行中重啟了服務(wù)器,或者在關(guān)閉數(shù)據(jù)庫時,將參數(shù)innodb_fast_shutdown設(shè)為了2時,下次MySQL數(shù)據(jù)庫啟動時都會對InnoDB存儲引擎的表進(jìn)行恢復(fù)操作。

參數(shù)innodb_force_recovery影響了整個InnoDB存儲引擎恢復(fù)的狀況。該參數(shù)值默認(rèn)為0,代表當(dāng)發(fā)生需要恢復(fù)時,進(jìn)行所有的恢復(fù)操作,當(dāng)不能進(jìn)行有效恢復(fù)時,如數(shù)據(jù)頁發(fā)生了corruption,MySQL數(shù)據(jù)庫可能發(fā)生宕機(jī)(crash),并把錯誤寫入錯誤日志中去。

但是,在某些情況下,可能并不需要進(jìn)行完整的恢復(fù)操作,因?yàn)橛脩糇约褐涝趺催M(jìn)行恢復(fù)。比如在對一個表進(jìn)行alter table操作時發(fā)生意外了,數(shù)據(jù)庫重啟時會對InnoDB表進(jìn)行回滾操作,對于一個大表來說這需要很長時間,可能是幾個小時。這時用戶可以自行進(jìn)行恢復(fù),如可以把表刪除,從備份中重新導(dǎo)入數(shù)據(jù)到表,可能這些操作的速度要遠(yuǎn)遠(yuǎn)快于回滾操作。

參數(shù)innodb_force_recovery還可以設(shè)置為6個非零值:1~6。大的數(shù)字表示包含了前面所有小數(shù)字表示的影響。具體情況如下:

1(SRV_FORCE_IGNORE_CORRUPT):忽略檢查到的corrupt頁。

2(SRV_FORCE_NO_BACKGROUND):阻止Master Thread線程的運(yùn)行,如Master Thread線程需要進(jìn)行full purge操作,而這會導(dǎo)致crash。

3(SRV_FORCE_NO_TRX_UNDO):不進(jìn)行事務(wù)的回滾操作。

4(SRV_FORCE_NO_IBUF_MERGE):不進(jìn)行插入緩沖的合并操作。

5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看撤銷日志(Undo Log),InnoDB存儲引擎會將未提交的事務(wù)視為已提交。

6(SRV_FORCE_NO_LOG_REDO):不進(jìn)行前滾的操作。

需要注意的是,在設(shè)置了參數(shù)innodb_force_recovery大于0后,用戶可以對表進(jìn)行select、create和drop操作,但insert、update和delete這類DML操作是不允許的。

InnoDB表

索引組織表

在InnoDB存儲引擎中,表都是根據(jù)主鍵順序組織存放的,這種存儲方式的表稱為索引組織表。每個表都有主鍵,如果在創(chuàng)建表時沒有顯示定義主鍵,則會按照如下方式選擇或者創(chuàng)建主鍵:

a.判定是否有非空的唯一索引,如果有則該列即為主鍵。若果有多個,則選擇建表是第一個定義的非空位于索引為主鍵。注意:主鍵的選擇根據(jù)的是定義索引的順序,而不是建表時的列的順序。

?b.如果不存在唯一索引,InnoDB存儲引擎字段創(chuàng)建一個6字節(jié)大小的指針。

InnoDB邏輯存儲結(jié)構(gòu)

從InnoDB存儲引擎的邏輯存儲結(jié)構(gòu)看,所有數(shù)據(jù)都被邏輯地存放在一個空間中,稱為表空間。表空間又由段(segment)、區(qū)(extent)、頁(page)組成。頁在一些文檔中有時也稱為塊(block),InnoDB存儲引擎的邏輯存儲結(jié)構(gòu)大致如圖:

InnoDB邏輯存儲結(jié)構(gòu)

表空間

表空間可以看做時InnoDB存儲引擎邏輯結(jié)構(gòu)的最高層,所有的數(shù)據(jù)都存放在表空間中。默認(rèn)情況下InnoDB只有一個共享表空間ibdata1,即所有的數(shù)據(jù)都存放在這個表空間中。如果用戶啟用了innodb_file_per_table,則每張表內(nèi)的數(shù)據(jù)可以單獨(dú)放到一個表空間內(nèi)。

需要注意的是,啟用了innodb_file_per_table參數(shù),每張表的表空間內(nèi)存放的只是數(shù)據(jù)、索引和插入緩沖Bitmap頁,其他類的數(shù)據(jù),如回滾信息,插入緩沖索引頁、系統(tǒng)事務(wù)信息,二次寫緩沖等還是存放在原來的共享表空間內(nèi)。

表空間是由各個段組成的,常見的段有數(shù)據(jù)段、索引段、回滾段等。因?yàn)镮nnoDB引擎表是索引組織的,因此數(shù)據(jù)即索引,索引即數(shù)據(jù)。那么數(shù)據(jù)段即為B+樹的葉子結(jié)點(diǎn),索引段即為B+樹的非葉子結(jié)點(diǎn)?;貪L段較為特殊,后面進(jìn)行介紹。

區(qū)

區(qū)是由連續(xù)頁組成的空間,在任何情況下每個區(qū)的大小都為1MB。為了保證區(qū)中頁的連續(xù)性,InnoDB一次從磁盤申請4-5個區(qū)。在默認(rèn)情況下,InnoDB存儲引擎頁的大小為16KB,即一個區(qū)中一共有64個連續(xù)的頁。

InnoDB 1.0版本開始引入壓縮頁,每個頁的大小可以設(shè)置為2K、4K、8K。

InnoDB 1.2版本新增參數(shù)innodb_page_size,可將默認(rèn)頁的大小設(shè)置為4K、8K。

頁是InnoDB磁盤管理的最小單位,在InnoDB存儲引擎中,默認(rèn)每個頁的大小為16KB。

在InnoDB存儲引擎中,常見的頁類型有:

數(shù)據(jù)頁

undo頁

系統(tǒng)頁

事務(wù)數(shù)據(jù)頁

插入緩沖位圖頁

插入緩沖空閑列表頁

未壓縮的二進(jìn)制大對象頁

壓縮的二進(jìn)制大對象頁

InnoDB數(shù)據(jù)是按照行進(jìn)行存放的。每個頁存放的行記錄也是有硬性定義的,最多允許存放16KB/ 2 - 200行的記錄,即7992行記錄。

InnoDB行記錄格式

InnoDB存儲引擎記錄是以行的形式存儲的。這意味著頁中保存著表中一行行的數(shù)據(jù)。

Compact行記錄格式

compact行記錄是由MySQL5.0引入的,其設(shè)計目標(biāo)是高效地存儲數(shù)據(jù)。簡單來說一個頁中存放的行數(shù)據(jù)越多,其性能就越高。

compact行記錄的格式

compact行記錄格式的首部是一個非Null變長字段長度列表,并且其是按照列的順序逆序放置的,其長度為:

若列的長度小于255,用一字節(jié)表示;

若大于255字節(jié),用2字節(jié)表示。

所以VARCHAR類型的最大長度限制為65535。

之后的第二個部分是NULL標(biāo)志位,該位指示了該行數(shù)據(jù)中是否有NULL值,有則用1表示,占用1字節(jié)。

接下來的部分是記錄頭信息,固定5字節(jié),具體含義見圖:

compact記錄頭信息

最后的部分就是實(shí)際存儲每個列的數(shù)據(jù)。需要注意的是,NULL不占該部分任何空間,即NULL除了占有NULL標(biāo)志位,實(shí)際存儲不占有任何空間。另外有一點(diǎn)需要注意的是,每行數(shù)據(jù)除了用戶定義的列外,還有兩個隱藏列,事務(wù)ID列和回滾指針列,分別為6字節(jié)和7字節(jié)的大小。若InnoDB表沒有定義主鍵,每行還會增加一個6字節(jié)的rowid列。

行溢出數(shù)據(jù)

InnoDB存儲引擎可以將一條記錄中的某些數(shù)據(jù)存儲在真正的數(shù)據(jù)頁面之外。一般默認(rèn)BLOB、LOB這類的大對象列類型就會存放在數(shù)據(jù)頁面之外。但他們也可以不將數(shù)據(jù)放在溢出頁面,即便是VARCHAR列數(shù)據(jù)類型,依然有可能被存放在行溢出數(shù)據(jù)。

在一般情況下,InnoDB存儲引擎的數(shù)據(jù)都是存放在頁類型為B-tree node中。但是當(dāng)發(fā)生行溢出時,數(shù)據(jù)存放在頁類型為Uncompress BLOB頁中。

行溢出數(shù)據(jù)的存儲

InnoDB表是索引組織的,即B+Tree的結(jié)構(gòu),這樣每個頁中至少有兩條行記錄。因此,如果頁中只能存放下一條記錄,那么InnoDB存儲引擎會自動將行數(shù)據(jù)存放到溢出頁中。

InnoDB數(shù)據(jù)頁結(jié)構(gòu)

InnoDB數(shù)據(jù)頁由以下七個部分組成,如圖所示:

File Header(文件頭)

Page Header(頁頭)

Infimun + Supremum Records

User Records(用戶記錄)

Free Space(空閑空間)

Page Directory(頁目錄)

File Trailer(文件結(jié)尾信息)

InnoDB存儲引擎數(shù)據(jù)頁結(jié)構(gòu)

File Header、Page Header、File Trailer的大小是固定的,用來標(biāo)示該頁的一些信息。其余部分為實(shí)際的行存儲空間,因此大小是動態(tài)的。

Infimun + Supremum Records

Infimun Supremum Records用來限定記錄的邊界,Infimun記錄是比該頁中任何主鍵值都小的值,Supremum記錄是比該頁中任何主鍵值都大的值。

Infimun和Supremum記錄

File Trailer

為了保證頁能夠完整地寫入磁盤(如寫入過程中遇到宕機(jī)、磁盤損壞等原因),InnoDB存儲引擎的頁中設(shè)置了File Trailer部分。

約束

數(shù)據(jù)完整性

關(guān)系型數(shù)據(jù)庫和文件系統(tǒng)的一個不同點(diǎn)是,關(guān)系數(shù)據(jù)庫本身能保證存儲數(shù)據(jù)的完整性,不需要應(yīng)用程序的控制,而文件系統(tǒng)一般需要在程序端進(jìn)行控制。幾乎所有的關(guān)系型數(shù)據(jù)庫都提供約束機(jī)制,約束提供了一條強(qiáng)大而簡易的途徑來保證數(shù)據(jù)庫中的數(shù)據(jù)完整性,數(shù)據(jù)完整性有三種形式:

實(shí)體完整性:保證表中有一個主鍵。在InnoDB中,我們可以通過定義Primary Key或者Unique Key約束來保證實(shí)體的完整性。

域完整性:保證數(shù)據(jù)的值滿足特定的條件。在InnoDB引擎中,域完整性通過以下幾種途徑來保證:選擇合適的數(shù)據(jù)類型可以確保一個數(shù)據(jù)值滿足特定條件,外鍵約束,編寫觸發(fā)器,還可以考慮DEFAULT約束作為強(qiáng)制域完整性的一個方面。

參照完整性:保證兩張表之間的關(guān)系。InnoDB引擎支持外鍵允許用戶定義外鍵以強(qiáng)制參照完整性。

對于InnoDB存儲引擎,提供了以下幾種約束:

Primary Key

Unique Key

Foreign Key

Default

NOT NULL

約束可以在表建立時就進(jìn)行定義,也可以在之后使用ALTER TABLE命令來進(jìn)行創(chuàng)建。

約束和索引的概念有所不同,約束更是一個邏輯的概念,用來保證數(shù)據(jù)的完整性,而索引是一個數(shù)據(jù)結(jié)構(gòu),有邏輯上的概念,在數(shù)據(jù)庫中更是一個物理存儲的方式。

默認(rèn)情況下,MySQL數(shù)據(jù)庫允許非法或者不正確的數(shù)據(jù)插入或更新,或者內(nèi)部將其轉(zhuǎn)化為一個合法的值,如NOT NULL字段插入一個NULL值,會將其更改為0再進(jìn)行插入,因此本身沒有對數(shù)據(jù)的正確性進(jìn)行約束。

MySQL不支持傳統(tǒng)的CHECK約束,但是通過ENUM和SET類型可以解決部分這樣的約束需求

觸發(fā)器與約束

前面小結(jié)介紹了,完整性約束通常也可以使用觸發(fā)器來實(shí)現(xiàn)。

觸發(fā)器的作用是在INSERT、DELETE、和UPDATE命令之前或之后自動調(diào)用SQL命令或者存儲過程。

創(chuàng)建觸發(fā)器的命令是CREATE TRIGGER,只有具備Super權(quán)限的MySQL用戶才可以執(zhí)行這條命令。

最多可以為一個表建立6個觸發(fā)器,即分別為INSERT、UPDATE、DELETE的BEFORE和AFTER各定義一個。

外鍵

外鍵用來保證參照完整性,MySQL的MyISAM引擎本身不支持外鍵,對于外鍵的定義只是起到一個注釋的作用。InnoDB引擎則支持外鍵約束。

我們可以在建表時就添加外鍵,也可以在之后通過ALTER TABLE命令添加。

視圖

視圖是一個命名的虛表,它由一個查詢來定義,可以當(dāng)做表使用。與持久表不同的是,視圖中的數(shù)據(jù)沒有物理表現(xiàn)的形式。

視圖的主要用途之一是被用做一個抽象裝置,特別是對于一些應(yīng)用程序,程序本身不需要關(guān)系基表的結(jié)構(gòu),只需要按照視圖定義來獲取數(shù)據(jù)或者更新數(shù)據(jù)。

雖然視圖是基于基表的一個虛擬表,但是我們可以對某些視圖進(jìn)行更新操作,其實(shí)就是通過視圖的定義來更新基本表。

分區(qū)表

分區(qū)功能并不是在存儲引擎層完成的,因此不只有InnoDB存儲引擎支持分區(qū),常見的存儲引擎MyISAM、NDB等都支持。但也并不是所有的存儲引擎都支持。

MySQL在5.1版本時添加了對于分區(qū)的支持這個過程是將一個表或者索引物分解為多個更小、更可管理的部分。就訪問數(shù)據(jù)庫的應(yīng)用而言,從邏輯上講,只有一個表或者一個索引,但是在物理上這個表或者索引可能由數(shù)十個物理分區(qū)組成。每個分區(qū)都是獨(dú)立的對象,可以獨(dú)自處理,也可以作為一個更大的對象的一部分進(jìn)行處理。

MySQL數(shù)據(jù)庫支持的分區(qū)類型為水平,并不支持垂直分區(qū)。此外,MySQL數(shù)據(jù)庫的分區(qū)是局部分區(qū)索引,一個分區(qū)中既存放了數(shù)據(jù)有存放了索引。

分區(qū)對于某些SQL語句性能可能會帶來提高,但是分區(qū)主要用于高可用性,利于數(shù)據(jù)庫的管理。

當(dāng)前MySQL數(shù)據(jù)庫支持以下幾種類型的分區(qū):

RANGE分區(qū):行數(shù)據(jù)基于屬于一個給定連續(xù)區(qū)間的列值放入分區(qū)。

LIST分區(qū):和RANGE分區(qū)類似,只是LIST分區(qū)面向的是離散的值。

HASH分區(qū):根據(jù)用戶自定義的表達(dá)式的返回值來進(jìn)行分區(qū),返回值不能為負(fù)數(shù)。

KEY分區(qū):根據(jù)MySQL數(shù)據(jù)庫提供的哈希函數(shù)來進(jìn)行分區(qū)。

不論創(chuàng)建何種類型的分區(qū),如果表中存在主鍵或者是唯一索引時,分區(qū)列必須是唯一索引的一個組成部分。

Columns分區(qū)

前面介紹的幾種分區(qū)中,分區(qū)的條件必須是整形。MySQL 5.5版本開始支持Columns分區(qū),可以視為RANGE和LIST分區(qū)的一種進(jìn)化。

Columns分區(qū)支持以下的數(shù)據(jù)類型:

所有整形類型

日期類型,如DATE和DATETIME

字符串類型,如CHAR、VARCHAR、BINARY和VARBINARY。BLOB和TEXT類型不予支持。

子分區(qū)

子分區(qū)是在分區(qū)的基礎(chǔ)上再進(jìn)行分區(qū),有時也稱這種分區(qū)為符合分區(qū)。MySQL允許RANGE和LIST的分區(qū)上再進(jìn)行HASH或者是KEY的子分區(qū)。

分區(qū)性能

數(shù)據(jù)庫的應(yīng)用分為兩類:一類是OLTP(在線事務(wù)處理),如博客、電子商務(wù)、網(wǎng)絡(luò)游戲等;另一類是OLAP(在線分析處理),如數(shù)據(jù)倉庫、數(shù)據(jù)集市。

對于OLAP的應(yīng)用,分區(qū)的確可以很好地提高查詢的性能,因?yàn)镺LAP應(yīng)用的大多數(shù)查詢需要頻繁地掃描一張很大的表。假設(shè)有一張1億行的表,其中有一個時間戳屬性列。你的查詢需要從這張表中獲取一年的數(shù)據(jù)。如果按時間戳進(jìn)行分區(qū),則只需要掃描相應(yīng)的分區(qū)即可。

對于OLTP的應(yīng)用,分區(qū)應(yīng)該非常小心。在這種應(yīng)用下,不可能會獲取一張大表中10%的數(shù)據(jù),大部分都是通過索引返回一條記錄即可。可根據(jù)B+樹索引的原理可知,對于一張大表,一般的B+樹需要2-3次磁盤IO。因此B+樹可以很好的完成操作,不需要分區(qū)的幫助。

參考

MySQL技術(shù)內(nèi)幕

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容