继夫的玩弄H辣文的小说|女人与拘性猛交视频|精品欧美高清不卡高清|一起做亏亏的事情的视频|啦啦啦在线视频观看|望月直播下载ios版本|国产日韩欧美一区二区三区

關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

大家都知道 MySQL 的數(shù)據(jù)都是保存在磁盤的,那具體是保存在哪個(gè)文件呢?MySQL 存儲(chǔ)的行為是由存儲(chǔ)引擎實(shí)現(xiàn)的 , MySQL 支持多種存儲(chǔ)引擎,不同的存儲(chǔ)引擎保存的文件自然也不同 。InnoDB 是我們常用的存儲(chǔ)引擎,也是 MySQL 默認(rèn)的存儲(chǔ)引擎 。本文主要以 InnoDB 存儲(chǔ)引擎展開(kāi)討論 。InnoDB簡(jiǎn)介
InnoDB是一個(gè)將表中的數(shù)據(jù)存儲(chǔ)到磁盤上的存儲(chǔ)引擎 。而真正處理數(shù)據(jù)的過(guò)程是發(fā)生在內(nèi)存中的,所以需要把磁盤中的數(shù)據(jù)加載到內(nèi)存中 , 如果是處理寫入或修改請(qǐng)求的話,還需要把內(nèi)存中的內(nèi)容刷新到磁盤上 。而我們知道讀寫磁盤的速度非常慢,和內(nèi)存讀寫差了幾個(gè)數(shù)量級(jí) 。所以當(dāng)我們想從表中獲取某些記錄時(shí) , InnoDB存儲(chǔ)引擎需要一條一條的把記錄從磁盤上讀出來(lái)么?想要了解這個(gè)問(wèn)題,我們首先需要了解InnoDB的存儲(chǔ)結(jié)構(gòu)是怎樣的 。

關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
InnoDB采取的方式是:將數(shù)據(jù)劃分為若干個(gè)頁(yè),以頁(yè)作為磁盤和內(nèi)存之間交互的基本單位innodb_page_size選項(xiàng)指定了MySQL實(shí)例的所有InnoDB表空間的頁(yè)面大小 。這個(gè)值是在創(chuàng)建實(shí)例時(shí)設(shè)置的,之后保持不變 。有效值為64KB,32KB,16KB(默認(rèn)值 ),8kB和4kB 。也就是在一般情況下,一次最少?gòu)拇疟P中讀取16KB的內(nèi)容到內(nèi)存中,一次最少把內(nèi)存中的16KB內(nèi)容刷新到磁盤中 。
InnoDB 行格式
我們平時(shí)是以記錄為單位來(lái)向表中插入數(shù)據(jù)的,這些記錄在磁盤上的存放方式也被稱為行格式或者記錄格式 。一行記錄可以以不同的格式存在InnoDB中,行格式分別是compact、redundant、dynamic和compressed行格式 。可以在創(chuàng)建或修改的語(yǔ)句中指定行格式:
— 創(chuàng)建數(shù)據(jù)表時(shí),顯示指定行格式CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名稱;– 創(chuàng)建數(shù)據(jù)表時(shí),修改行格式ALTER TABLE 表名 ROW_FORMAT=行格式名稱;– 查看數(shù)據(jù)表的行格式show table status like ;
mysql5.0之前默認(rèn)的行格式是redundant,mysql5.0之后的默認(rèn)行格式為compact , 5.7之后的默認(rèn)行格式為dynamic
compact格式
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
記錄的額外信息
記錄的額外信息:分別是變長(zhǎng)字段長(zhǎng)度列表、NULL值列表和記錄頭信息
1:變長(zhǎng)字段長(zhǎng)度列表
mysql中支持一些變長(zhǎng)數(shù)據(jù)類型(比如VARCHAR(M)、TEXT等),它們存儲(chǔ)數(shù)據(jù)占用的存儲(chǔ)空間不是固定的,而是會(huì)隨著存儲(chǔ)內(nèi)容的變化而變化 。在Compact行格式中,把所有變長(zhǎng)字段的真實(shí)數(shù)據(jù)占用的字節(jié)長(zhǎng)度都存放在記錄的開(kāi)頭部位,從而形成一個(gè)變長(zhǎng)字段長(zhǎng)度列表,各變長(zhǎng)字段數(shù)據(jù)占用的字節(jié)數(shù)按照列的順序逆序存放
變長(zhǎng)字段長(zhǎng)度列表中只存儲(chǔ)值為 非NULL 的列內(nèi)容占用的長(zhǎng)度,值為 NULL 的列的長(zhǎng)度是不儲(chǔ)存的。
并不是所有記錄都有這個(gè) 變長(zhǎng)字段長(zhǎng)度列表 部分,比方說(shuō)表中所有的列都不是變長(zhǎng)的數(shù)據(jù)類型的話 , 這一部分就不需要有
2:NULL值列表
NULL值列表:Compact格式會(huì)把所有可以為NULL的列統(tǒng)一管理起來(lái),存在一個(gè)NULL值列表,如果表中沒(méi)有允許為NULL的列 , 則NULL值列表也不復(fù)存在了 。
為什么要有NULL值列表?
表中的某些列可能存儲(chǔ)NULL值,如果把這些NULL值都放到記錄的真實(shí)數(shù)據(jù)中存儲(chǔ)會(huì)很浪費(fèi)空間,所以Compact行格式把這些值為NULL的列統(tǒng)一管理起來(lái) , 存儲(chǔ)到NULL值列表中,它的處理過(guò)程是這樣的:
首先統(tǒng)計(jì)表中允許存儲(chǔ)NULL的列有哪些 。
根據(jù)列的實(shí)際值,用0或者1填充NULL值列表,1代表該列的值為空,0代表該列的值不為空 。
如果表中沒(méi)有允許存儲(chǔ) NULL 的列,則 NULL值列表 也不存在了 。
3:記錄頭信息
名稱
大小(單位:bit)
描述
預(yù)留位1
1
未使用
預(yù)留位2
1
未使用
delete_mask
1
標(biāo)記改記錄是否被刪除
min_rec_mask
1
B+樹(shù)非葉子節(jié)點(diǎn)中最小記錄都會(huì)添加該標(biāo)記
n_owned
4
當(dāng)前記錄擁有的記錄數(shù)
heap_no
13
當(dāng)前記錄在記錄堆的位置信息
record_type
3
記錄類型 
0:普通記錄 
1:B+樹(shù)非葉子節(jié)點(diǎn)記錄
2:最小記錄
3:最大記錄
next_record
16
下一條記錄的相對(duì)位置
redundant 格式
與compact 格式相比, 沒(méi)有了 變長(zhǎng)字段列表以及 NULL值列表, 取而代之的是 記錄了所有真實(shí)數(shù)據(jù)的偏移地址表  , 偏移地址表 是倒序排放的, 但是計(jì)算偏移量卻還是正序開(kāi)始的從row_id作為第一個(gè), 第一個(gè)從0開(kāi)始累加字段對(duì)應(yīng)的字節(jié)數(shù) 。在記錄頭信息中, 大部分字段和compact 中的相同,但是對(duì)比compact多了 。
n_field(記錄列的數(shù)量)、1byte_offs_flag(字段長(zhǎng)度列表每一列占用的字節(jié)數(shù)),少了record_type字段 。
因?yàn)閞edundant是mysql 5.0 以前就在使用的一種格式, 已經(jīng)非常古老, 使用頻率非常的低,這里就不過(guò)多表述 。
dynamic 格式
在現(xiàn)在 mysql 5.7 的版本中,使用的格式就是 dynamic 。
dynamic 和 compact 基本是相同的,只有在溢出頁(yè)的處理上面,有所不同 。
在compact行格式中,對(duì)于占用存儲(chǔ)空間非常大的列,在記錄的真實(shí)數(shù)據(jù)處只會(huì)存儲(chǔ)該列的前768個(gè)字節(jié)的數(shù)據(jù),把剩余的數(shù)據(jù)分散存儲(chǔ)在幾個(gè)其他的頁(yè)中,然后記錄的真實(shí)數(shù)據(jù)處用20個(gè)字節(jié)存儲(chǔ)指向這些頁(yè)的地址 , 從而可以找到剩余數(shù)據(jù)所在的頁(yè) 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
這種在本記錄的真實(shí)數(shù)據(jù)處只會(huì)存儲(chǔ)該列的前768個(gè)字節(jié)的數(shù)據(jù)和一個(gè)指向其他頁(yè)的地址 , 然后把剩下的數(shù)據(jù)存放到其他頁(yè)中的情況就叫做行溢出,存儲(chǔ)超出768字節(jié)的那些頁(yè)面也被稱為溢出頁(yè)(uncompresse blob page) 。
dynamic中會(huì)直接在真實(shí)數(shù)據(jù)區(qū)記錄 20字節(jié) 的溢出頁(yè)地址, 而不再去額外記錄一部分的數(shù)據(jù)了 。
行溢出臨界點(diǎn)
MySQL中規(guī)定一個(gè)頁(yè)中至少存放兩行記錄 。簡(jiǎn)單理解:因?yàn)锽+樹(shù)的特性,如果不存儲(chǔ)至少2條記錄,則這個(gè)B+樹(shù)是沒(méi)有意義的,形不成一個(gè)有效的索引 。
每個(gè)頁(yè)除了存放我們的記錄以外,也需要存儲(chǔ)一些額外的信息,大概132個(gè)字節(jié) 。
每個(gè)記錄需要的額外信息是27字節(jié) 。假設(shè)一個(gè)列中存儲(chǔ)的數(shù)據(jù)字節(jié)數(shù)為n,如要要保證該列不發(fā)生溢出 , 則需要滿足:132 + 2×(27 + n)
compressed 格式
compressed 格式將會(huì)在Dynamic 的基礎(chǔ)上面進(jìn)行壓縮處理特別是對(duì)溢出頁(yè)的壓縮處理 , 存儲(chǔ)在其中的行數(shù)據(jù)會(huì)以zlib的算法進(jìn)行壓縮 , 因此對(duì)于blob、text這類大長(zhǎng)度類型的數(shù)據(jù)能夠進(jìn)行非常有效的存儲(chǔ) 。但compressed格式其實(shí)也是以時(shí)間換空間 , 性能并不友好 , 并不推薦在常見(jiàn)的業(yè)務(wù)中使用 。
InnoDB 數(shù)據(jù)頁(yè)結(jié)構(gòu)
數(shù)據(jù)頁(yè)代表的這塊16KB大小的存儲(chǔ)空間可以被劃分為多個(gè)部分,不同部分有不同的功能
名稱
中文名
大小
描述
File Header
文件頭部
38字節(jié)
頁(yè)通用信息
Page Header
頁(yè)面頭部
56字節(jié)
頁(yè)專有信息
infimun + supermun
最小記錄和最大記錄
26字節(jié)
虛擬的行記錄
User Rcords
用戶記錄
不確定
實(shí)際存儲(chǔ)的行記錄內(nèi)容
Free Space
空閑空間
不確定
頁(yè)中未使用的空間
Page Directory
頁(yè)面目錄
不確定
頁(yè)中一些記錄的相對(duì)位置
File Tariler
文件尾部
8字節(jié)
校驗(yàn)頁(yè)的完整性
每當(dāng)我們插入一條記錄 , 都會(huì)從Free Space部分,也就是尚未使用的存儲(chǔ)空間中申請(qǐng)一個(gè)記錄大小的空間劃分到User Records部分,當(dāng)Free Space部分的空間全部被User Records部分替代掉之后,也就意味著這個(gè)頁(yè)使用完了 , 如果還有新的記錄插入的話 , 就需要去申請(qǐng)新的頁(yè)了,這個(gè)過(guò)程的圖示如下:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
為了方便講述
先創(chuàng)建一個(gè)表: CREATE TABLE test(         a1 INT,         a2 INT,         a3 VARCHAR(100),         PRIMARY KEY (a1)     ) CHARSET=ascii ROW_FORMAT=Compact;test表中插入幾條記錄:INSERT INTO test VALUES(1, 10, aaa); INSERT INTO test VALUES(2, 20, bbb); INSERT INTO test VALUES(3, 30, ccc); INSERT INTO test VALUES(4, 40, ddd);
這些記錄,就如下圖所示,存儲(chǔ)在User Rcords里
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
delete_mask這個(gè)屬性標(biāo)記著當(dāng)前記錄是否被刪除 。這些被刪除的記錄之所以不立即從磁盤上移除,是因?yàn)橐瞥鼈冎蟀哑渌挠涗浽诖疟P上重新排列需要性能消耗,所以只是打一個(gè)刪除標(biāo)記而已 。所有被刪除掉的記錄都會(huì)組成一個(gè)所謂的垃圾鏈表,在這個(gè)鏈表中的記錄占用的空間稱之為所謂的可重用空間 , 之后如果有新記錄插入到表中的話,可能把這些被刪除的記錄占用的存儲(chǔ)空間覆蓋掉 。
min_rec_maskB+樹(shù)的每層非葉子節(jié)點(diǎn)中的最小記錄都會(huì)添加該標(biāo)記,min_rec_mask值都是0,意味著它們都不是B+樹(shù)的非葉子節(jié)點(diǎn)中的最小記錄 。
n_owned在頁(yè)目錄分組時(shí)使用,每個(gè)組的最后一條記錄(也就是組內(nèi)最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內(nèi)共有幾條記錄 。
heap_no這個(gè)屬性表示當(dāng)前記錄在本頁(yè)中的位置,從圖中可以看出來(lái),我們插入的4條記錄在本頁(yè)中的位置分別是:2、3、4、5 。heap_no值為0和1的記錄,稱為偽記錄或者虛擬記錄 。這兩個(gè)偽記錄一個(gè)代表最小記錄 , 一個(gè)代表最大記錄 。
record_type這個(gè)屬性表示當(dāng)前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹(shù)非葉節(jié)點(diǎn)記錄,2表示最小記錄,3表示最大記錄 。
next_record它表示從當(dāng)前記錄的真實(shí)數(shù)據(jù)到下一條記錄的真實(shí)數(shù)據(jù)的地址偏移量 。比方說(shuō)第一條記錄的next_record值為32,意味著從第一條記錄的真實(shí)數(shù)據(jù)的地址處向后找32個(gè)字節(jié)便是下一條記錄的真實(shí)數(shù)據(jù) 。下一條記錄指得并不是按照我們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄 。而且規(guī)定Infimum記錄(也就是最小記錄) 的下一條記錄就是本頁(yè)中主鍵值最小的用戶記錄,而本頁(yè)中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄) 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
從圖中可以看出來(lái) , 我們的記錄按照主鍵從小到大的順序形成了一個(gè)單鏈表 。
Page Directory(頁(yè)目錄)
現(xiàn)在我們了解了記錄在頁(yè)中按照主鍵值由小到大順序串聯(lián)成一個(gè)單鏈表,單向鏈表的特點(diǎn)就是插入、刪除非常方便,但是檢索效率不高,最差的情況下需要遍歷鏈表上的所有節(jié)點(diǎn)才能完成檢索 。因此在頁(yè)結(jié)構(gòu)中專門設(shè)計(jì)了頁(yè)目錄這個(gè)模塊 , 專門給記錄做一個(gè)目錄,通過(guò)二分查找法的方式進(jìn)行檢索,提升效率 。
1:將所有正常的記錄(包括最大和最小記錄 , 不包括標(biāo)記為已刪除的記錄)劃分為幾個(gè)組 。
2:每個(gè)組的最后一條記錄(也就是組內(nèi)最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內(nèi)共有幾條記錄 。
3:將每個(gè)組的最后一條記錄的地址偏移量單獨(dú)提取出來(lái),用作查找 。
注意:這個(gè)頁(yè)目錄是為主鍵服務(wù)的 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
需要注意的是:
第一:第一個(gè)小組,也就是最小記錄所在的分組只能有1個(gè)記錄;
第二:最后一個(gè)小組,就是最大記錄所在的分組,只能有1-8條記錄;
第三:剩下的分組中記錄的條數(shù)范圍只能在是 4-8 條之間;
分組是按照下邊的步驟進(jìn)行:
初始情況下一個(gè)數(shù)據(jù)頁(yè)里只有最小記錄和最大記錄兩條記錄,它們分屬于兩個(gè)分組 。
之后每插入一條記錄,都會(huì)從頁(yè)目錄中找到主鍵值比本記錄的主鍵值大并且差值最小的槽,然后把該槽對(duì)應(yīng)的記錄的n_owned值加1 , 表示本組內(nèi)又添加了一條記錄,直到該組中的記錄數(shù)等于8個(gè) 。
【關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?】在一個(gè)組中的記錄數(shù)等于8個(gè)后再插入一條記錄時(shí),會(huì)將組中的記錄拆分成兩個(gè)組,一個(gè)組中4條記錄,另一個(gè)5條記錄 。這個(gè)過(guò)程會(huì)在頁(yè)目錄中新增一個(gè)槽來(lái)記錄這個(gè)新增分組中最大的那條記錄的偏移量 。
我們?cè)偬砑?條記錄看看效果:
INSERT INTO test VALUES(5, 50, eee); INSERT INTO test VALUES(6, 60, fff); INSERT INTO test VALUES(7, 70, ggg); INSERT INTO test VALUES(8, 80, hhh); INSERT INTO test VALUES(9, 90, iii);INSERT INTO test VALUES(10, 100, jjj);INSERT INTO test VALUES(11, 110, kkk);INSERT INTO test VALUES(12, 120, lll);
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
這里為了便于理解,圖中只保留了用戶記錄頭信息中的n_owned和next_record屬性 。
因?yàn)楦鱾€(gè)槽代表的記錄的主鍵值都是從小到大排序的 , 所以我們可以使用二分法來(lái)進(jìn)行快速查找 。
所以在一個(gè)數(shù)據(jù)頁(yè)中查找指定主鍵值的記錄的過(guò)程分為兩步:
1.通過(guò)二分法確定該記錄所在的槽,并找到該槽所在分組中主鍵值最大的那條記錄 。
2.通過(guò)記錄的next_record屬性遍歷該槽所在的組中的各個(gè)記錄 。
比方說(shuō)我們查找主鍵值為x的記錄,計(jì)算中間槽的位置(min+max)/2 =mid,查看mid槽對(duì)應(yīng)的主鍵值y , 若x
y,則max不變 , min=mid 。依此類推 。
舉例:我們想找主鍵值為6的記錄,過(guò)程是這樣的計(jì)算中間槽的位置:(0+3)/2=1,所以查看槽1對(duì)應(yīng)記錄的主鍵值為4 , 因?yàn)?
注意:若查到數(shù)據(jù)在槽2的分組中,由于槽2是指向最后一個(gè)記錄,所以需要向上找一個(gè)槽位,定位到上一個(gè)槽位最后一行 , 然后再向下找 。
File Header(文件頭部)
File Header針對(duì)各種類型的頁(yè)都通用,也就是說(shuō)不同類型的頁(yè)都會(huì)以File Header作為第一個(gè)組成部分 , 它描述了一些針對(duì)各種頁(yè)都通用的一些信息,比方說(shuō)這個(gè)頁(yè)的編號(hào)是多少 , 它的上一個(gè)頁(yè)、下一個(gè)頁(yè)是誰(shuí)等 。
FIL_PAGE_OFFSET每一個(gè)頁(yè)都有一個(gè)單獨(dú)的頁(yè)號(hào),就跟你的身份證號(hào)碼一樣,InnoDB通過(guò)頁(yè)號(hào)來(lái)可以唯一定位一個(gè)頁(yè) 。
FIL_PAGE_PREV和FIL_PAGE_NEXTFIL_PAGE_PREV和FIL_PAGE_NEXT就分別代表本頁(yè)的上一個(gè)和下一個(gè)頁(yè)的頁(yè)號(hào) 。這樣通過(guò)建立一個(gè)雙向鏈表把許許多多的頁(yè)就都串聯(lián)起來(lái)了 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
B+樹(shù)索引
InnoDB數(shù)據(jù)頁(yè)的主要組成部分 。各個(gè)數(shù)據(jù)頁(yè)可以組成一個(gè)雙向鏈表 , 而每個(gè)數(shù)據(jù)頁(yè)中的記錄會(huì)按照主鍵值從小到大的順序組成一個(gè)單向鏈表,每個(gè)數(shù)據(jù)頁(yè)都會(huì)為存儲(chǔ)在它里邊兒的記錄生成一個(gè)頁(yè)目錄 。再通過(guò)主鍵查找某條記錄的時(shí)候可以在頁(yè)目錄中使用二分法快速定位到對(duì)應(yīng)的槽 。
在一個(gè)頁(yè)中的查找:
以主鍵為搜索條件這個(gè)查找過(guò)程我們已經(jīng)很熟悉了 , 可以在頁(yè)目錄中使用二分法快速定位到對(duì)應(yīng)的槽,然后再遍歷該槽對(duì)應(yīng)分組中的記錄即可快速找到指定的記錄 。
以其他列作為搜索條件對(duì)非主鍵列的查找的過(guò)程可就不這么幸運(yùn)了,因?yàn)樵跀?shù)據(jù)頁(yè)中并沒(méi)有對(duì)非主鍵列建立所謂的頁(yè)目錄 , 所以我們無(wú)法通過(guò)二分法快速定位相應(yīng)的槽 。這種情況下只能從最小記錄開(kāi)始依次遍歷單鏈表中的每條記錄,然后對(duì)比每條記錄是不是符合搜索條件 。
在很多頁(yè)中查找:
1:定位到記錄所在的頁(yè) 。
2:從所在的頁(yè)內(nèi)中查找相應(yīng)的記錄 。
在沒(méi)有索引的情況下,不論是根據(jù)主鍵列或者其他列的值進(jìn)行查找,由于我們并不能快速的定位到記錄所在的頁(yè),所以只能從第一個(gè)頁(yè)沿著雙向鏈表一直往下找,在每一個(gè)頁(yè)中根據(jù)我們上面聊過(guò)的查找方式去查找指定的記錄 。
索引
同樣的 , 我們以上面建的表test為例,清空插入的數(shù)據(jù) , 此時(shí)test表為一張空數(shù)據(jù)的表,為了便于講述,我們可以簡(jiǎn)單的把test表的行格式理解如下:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
一個(gè)簡(jiǎn)單的索引方案:我們?yōu)楦鶕?jù)主鍵值快速定位一條記錄在頁(yè)中的位置而設(shè)立的頁(yè)目錄,目錄中記錄的數(shù)據(jù)頁(yè)需要滿足下一個(gè)數(shù)據(jù)頁(yè)中用戶記錄的主鍵值必須大于上一個(gè)頁(yè)中用戶記錄的主鍵值 。
假設(shè)我們的每個(gè)數(shù)據(jù)頁(yè)最多能存放3條記錄(實(shí)際上一個(gè)數(shù)據(jù)頁(yè)非常大,可以存放下很多記錄),這時(shí)候我們向test表插入三條記錄,那么數(shù)據(jù)頁(yè)就如圖所示:
test表中插入幾條記錄:INSERT INTO test VALUES(1, 10, aa); INSERT INTO test VALUES(2, 20, bb); INSERT INTO test VALUES(4, 40, dd);
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
此時(shí)我們?cè)賮?lái)插入一條記錄:
INSERT INTO test VALUES(3, 30, cc);
因?yàn)樯厦娑x了,一個(gè)頁(yè)最多只能放3條記錄,所以我們不得不再分配一個(gè)新頁(yè):
頁(yè)1中用戶記錄最大的主鍵值是4 , 而頁(yè)2中有一條記錄的主鍵值是3,因?yàn)? > 3,所以這就不符合下一個(gè)數(shù)據(jù)頁(yè)中用戶記錄的主鍵值必須大于上一個(gè)頁(yè)中用戶記錄的主鍵值的要求,所以在插入主鍵值為3的記錄的時(shí)候需要伴隨著一次記錄移動(dòng),也就是把主鍵值為4的記錄移動(dòng)到頁(yè)2中,然后再把主鍵值為3的記錄插入到頁(yè)1中 。最后形成如圖所示 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
這個(gè)過(guò)程叫做頁(yè)分裂 。
真實(shí)數(shù)據(jù)存儲(chǔ)中,數(shù)據(jù)頁(yè)的編號(hào)并不是連續(xù)的,當(dāng)我們?cè)趖est表中插入多條記錄后,可能是這樣的效果:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
因?yàn)檫@些16KB的頁(yè)在物理存儲(chǔ)上可能并不挨著,所以如果想從這么多頁(yè)中根據(jù)主鍵值快速定位某些記錄所在的頁(yè),我們需要給它們做個(gè)目錄,每個(gè)頁(yè)對(duì)應(yīng)一個(gè)目錄項(xiàng),每個(gè)目錄項(xiàng)由頁(yè)中記錄的最小主鍵值和頁(yè)號(hào)組成 。我們?yōu)樯厦鎺讉€(gè)頁(yè)做目錄 , 則如圖:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
比方說(shuō)我們想找主鍵值為5的記錄,具體查找過(guò)程分兩步:
1:先從目錄項(xiàng)中根據(jù)二分法快速確定出主鍵值為5的記錄在目錄2中(因?yàn)?4
2:再根據(jù)前邊說(shuō)的在頁(yè)中查找記錄的方式去頁(yè)23中定位具體的記錄 。
這個(gè)目錄有一個(gè)別名 , 稱為索引 。
InnoDB中的索引方案
在InnoDB中復(fù)用了之前存儲(chǔ)用戶記錄的數(shù)據(jù)頁(yè)來(lái)存儲(chǔ)目錄項(xiàng),為了和用戶記錄做一下區(qū)分,我們把這些用來(lái)表示目錄項(xiàng)的記錄稱為目錄項(xiàng)記錄 。
用record_type來(lái)區(qū)分普通的用戶記錄還是目錄項(xiàng)記錄 。
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
如果表中的數(shù)據(jù)太多,以至于一個(gè)數(shù)據(jù)頁(yè)不足以存放所有的目錄項(xiàng)記錄 , 會(huì)再多整一個(gè)存儲(chǔ)目錄項(xiàng)記錄的頁(yè) 。所以如果此時(shí)我們?cè)傧蛏蠄D中插入一條主鍵值為10的用戶記錄的話:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
在查詢時(shí)我們需要定位存儲(chǔ)目錄項(xiàng)記錄的頁(yè),但是這些頁(yè)在存儲(chǔ)空間中也可能不挨著,如果我們表中的數(shù)據(jù)非常多則會(huì)產(chǎn)生很多存儲(chǔ)目錄項(xiàng)記錄的頁(yè) , 那我們?cè)趺锤鶕?jù)主鍵值快速定位一個(gè)存儲(chǔ)目錄項(xiàng)記錄的頁(yè)呢?其實(shí)也簡(jiǎn)單,為這些存儲(chǔ)目錄項(xiàng)記錄的頁(yè)再生成一個(gè)更高級(jí)的目錄,就像是一個(gè)多級(jí)目錄一樣,大目錄里嵌套小目錄,小目錄里才是實(shí)際的數(shù)據(jù),所以現(xiàn)在各個(gè)頁(yè)的示意圖就是這樣子:
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
用戶記錄其實(shí)都存放在B+樹(shù)的最底層的節(jié)點(diǎn)上,這些節(jié)點(diǎn)也被稱為葉子節(jié)點(diǎn)或葉節(jié)點(diǎn),其余用來(lái)存放目錄項(xiàng)的節(jié)點(diǎn)稱為非葉子節(jié)點(diǎn)或者內(nèi)節(jié)點(diǎn) , 其中B+樹(shù)最上邊的那個(gè)節(jié)點(diǎn)也稱為根節(jié)點(diǎn) 。
聚簇索引
我們上邊介紹的B+樹(shù)本身就是一個(gè)目錄,或者說(shuō)本身就是一個(gè)索引 。它有兩個(gè)特點(diǎn):
1:使用記錄主鍵值的大小進(jìn)行記錄和頁(yè)的排序
2:B+樹(shù)的葉子節(jié)點(diǎn)存儲(chǔ)的是完整的用戶記錄 。
我們把具有這兩種特性的B+樹(shù)稱為聚簇索引,所有完整的用戶記錄都存放在這個(gè)聚簇索引的葉子節(jié)點(diǎn)處 。這種聚簇索引并不需要我們?cè)贛ySQL語(yǔ)句中顯式的使用INDEX語(yǔ)句去創(chuàng)建,InnoDB存儲(chǔ)引擎會(huì)自動(dòng)的為我們創(chuàng)建聚簇索引 。另外有趣的一點(diǎn)是,在InnoDB存儲(chǔ)引擎中,聚簇索引就是數(shù)據(jù)的存儲(chǔ)方式(所有的用戶記錄都存儲(chǔ)在了葉子節(jié)點(diǎn)) , 也就是所謂的索引即數(shù)據(jù) , 數(shù)據(jù)即索引 。
二級(jí)索引
關(guān)于Mysql數(shù)據(jù)存儲(chǔ),你了解多少?

文章插圖
這個(gè)B+樹(shù)與上邊介紹的聚簇索引有幾處不同:
使用記錄a2列的大小進(jìn)行記錄和頁(yè)的排序
頁(yè)內(nèi)的記錄是按照a2列的大小順序排成一個(gè)單向鏈表 。
各個(gè)存放用戶記錄的頁(yè)也是根據(jù)頁(yè)中記錄的a2列大小順序排成一個(gè)雙向鏈表 。
存放目錄項(xiàng)記錄的頁(yè)分為不同的層次,在同一層次中的頁(yè)也是根據(jù)頁(yè)中目錄項(xiàng)記錄的a2列大小順序排成一個(gè)雙向鏈表 。
B+樹(shù)的葉子節(jié)點(diǎn)存儲(chǔ)的并不是完整的用戶記錄,而只是a2列+主鍵這兩個(gè)列的值 。
目錄項(xiàng)記錄中不再是主鍵+頁(yè)號(hào)的搭配,而變成了a2列+頁(yè)號(hào)的搭配 。
索引的代價(jià)
1:空間上的代價(jià)每建立一個(gè)索引都要為它建立一棵B+樹(shù),每一棵B+樹(shù)的每一個(gè)節(jié)點(diǎn)都是一個(gè)數(shù)據(jù)頁(yè),一個(gè)頁(yè)默認(rèn)會(huì)占用16KB的存儲(chǔ)空間 。
2:時(shí)間上的代價(jià)每次對(duì)表中的數(shù)據(jù)進(jìn)行增、刪、改操作時(shí),都需要去修改各個(gè)B+樹(shù)索引 。
B+樹(shù)每層節(jié)點(diǎn)都是按照索引列的值從小到大的順序排序而組成了雙向鏈表 。不論是葉子節(jié)點(diǎn)中的記錄,還是內(nèi)節(jié)點(diǎn)中的記錄(也就是不論是用戶記錄還是目錄項(xiàng)記錄)都是按照索引列的值從小到大的順序而形成了一個(gè)單向鏈表 。而增、刪、改操作可能會(huì)對(duì)節(jié)點(diǎn)和記錄的排序造成破壞,所以存儲(chǔ)引擎需要額外的時(shí)間進(jìn)行一些記錄移位,頁(yè)面分裂、頁(yè)面回收等操作來(lái)維護(hù)好節(jié)點(diǎn)和記錄的排序 。
總結(jié)
通過(guò)對(duì)InnoDB存儲(chǔ)邏輯分析 , 我們可以清楚的了解到mysql中是怎樣對(duì)數(shù)據(jù)進(jìn)行存儲(chǔ)的 。并且對(duì)索引樹(shù)的結(jié)構(gòu)進(jìn)行分析 , 幫助我們?cè)诠ぷ髦懈雍侠淼氖褂盟饕?。