MySQL 中的表數(shù)據(jù)是如何存儲的?
MySQL是基于磁盤進(jìn)行數(shù)據(jù)存儲的關(guān)系型數(shù)據(jù)庫, 所有的數(shù)據(jù)和索引都以磁盤文件的方式存儲, 在有需要時載入內(nèi)存讀取。MySQL支持多種存儲引擎,不同的存儲引擎保存的文件不同。InnoDB是MySQL使用最為廣泛的存儲引擎,下面我們以InnoDB引擎為例來說明。
當(dāng)我們創(chuàng)建一個數(shù)據(jù)庫表時,InnoDB會創(chuàng)建三個文件。其中,ibd后綴的文件是對表數(shù)據(jù)和索引實際存儲的文件,也稱為表空間文件。
表空間文件的結(jié)構(gòu)由段、區(qū)、頁、和行組成。
數(shù)據(jù)庫表中的記錄都是按行進(jìn)行存放的,每行記錄根據(jù)不同的行格式,有不同的存儲結(jié)構(gòu)。以Compact行格式為例,一條記錄的存儲可以分為兩部分,分別是“額外信息”和“真實數(shù)據(jù)”。 額外信息用來描述記錄,分為變長字段列表、NULL值列表和記錄頭信息。
雖然記錄是按照行來存儲的,但考慮到效率,數(shù)據(jù)庫的讀寫并不以行為單位,而是以頁為單位,一個頁中可以存儲多個行記錄。頁的大小默認(rèn)為16KB,也就是數(shù)據(jù)庫一次最少從磁盤中讀取16KB的內(nèi)容到內(nèi)存,一次最少把內(nèi)存中16KB的內(nèi)容刷新到磁盤。
區(qū)是比頁大一級的存儲結(jié)構(gòu),一個區(qū)會分配64 個連續(xù)的頁。因為頁大小默認(rèn)是16KB,所以一個區(qū)的大小是 64個16KB,也就是1MB。
段由一個或多個區(qū)組成,區(qū)在文件系統(tǒng)是一個連續(xù)分配的空間,不過在段中不要求區(qū)與區(qū)之間是相鄰的。段是數(shù)據(jù)庫中的分配單位,不同類型的數(shù)據(jù)庫對象以不同的段形式存在。當(dāng)我們創(chuàng)建數(shù)據(jù)表、索引的時候,就會創(chuàng)建對應(yīng)的段,比如創(chuàng)建一張表時會創(chuàng)建一個表段,創(chuàng)建一個索引時會創(chuàng)建一個索引段。
表空間存儲的對象是段,在一個表空間中可以有一個或多個段,但是一個段只能屬于一個表空間。數(shù)據(jù)庫由一個或多個表空間組成。
對于一行數(shù)據(jù)如何存放,我們需要重點關(guān)注變長字段和NULL值。MySQL支持一些變長的數(shù)據(jù)類型,比如varchar(n),變長字段中存儲多少字節(jié)數(shù)據(jù)是不固定的,所以我們存儲真實數(shù)據(jù)的時候,需要把這些數(shù)據(jù)占用的字節(jié)數(shù)也存起來。
在Compact行格式中,把所有變長字段真實數(shù)據(jù)占用的字節(jié)長度,按照記錄列的逆序方式存放在記錄的開頭部位,形成一個變長字段長度列表。注意當(dāng)數(shù)據(jù)表沒有變長字段時,比如全部都是int類型字段,這時候表里的行格式就不會有變長字段長度列表。
另外,Compact行格式還會把可以為NULL的列統(tǒng)一管理起來,以記錄列的逆序方式存放在一個NULL值列表中。標(biāo)示數(shù)據(jù)是否為NULL使用一個二進(jìn)制位表示,1為NULL,0為非NULL,注意,如果表中沒有允許存儲 NULL 的列,則 NULL值列表也不存在。