在這之前你需要對索引的存儲方式有一定的了解,這里我們只講表空間的結構,不講概念相關的東西。其次我們應該知道數據的存儲結構的設計很大一部分原因是為了加速數據的查找和插入,也就是取數據和村數據。
1. 表空間結構
在圖1和圖2中,里面的extent
是區
的意思,我們知道Mysql是以頁(一個頁16KB)方式進行數據存儲的,這里的區是頁的集合,64去頁組成一個區,一個區就是16KB*64=1MB
大小。
知道了區
之后,組
就很好說了,256個區組成一個組(如圖1)。另外需要提前說明的是圖2中的extent0、extent256、extentn表示組開頭的第一個區。
這里我們需要注意的重點是圖2每個區右邊箭頭指向的方塊,每個方塊表示一個頁。這里並沒有將所有的頁按照順序畫出來,只畫出了我們關心的幾個頁類型。
首先是FSP_HDR
,表空間中第一個組中的第一個區中的第一個頁是FSP_HDR
類型的頁。在FSP_HDR
頁后面是IBUF_BITMAP
類型的頁(並沒有畫出來,因為我們不講他),緊跟其后的是一個INODE
類型的頁,到此我們就引出了我們關鍵的兩個類型的頁了,第一個區后面的61個頁我們先不管。
然后是表空間中第一個組后面的其他組中每個組開頭的第一個頁是XDES
類型的頁。我們可以暫且將XDES
與FSP_HDR
看作是一個類型的頁(他們都存儲了XDES Entry
,但是FSP_HDR
存儲了一些表空間相關的屬性),也就是說每個組的開頭都有一個XDES
類型的頁。這樣我們就可以很快定位到表空間中所有的XDES
頁了,只需要知道是哪個表空間,然后位移固定的長度,就可以找到所有的XDES
頁了,因為每個組的大小是固定的。
_____________ ____________ _______________
| extent 0 | | | ---> | FSP_HDR(16KB) |
|-------------| | | ---------------
| ... | | extent 0 | _______________
|-------------| | | ---> | INODE(16KB) |
| extent 255 | | | ---------------
|-------------| |------------| _______________
| extent 256 | | | ---> | XDES(16KB) |
|-------------| | | ---------------
| ... | | extent 256 |
|-------------| | |
| extent 511 | |------------| _______________
| ... | | extent n | ---> | XDES(16KB) |
------------- ------------ ---------------
圖1(組結構) 圖2(區)
2. XDES類型的頁
XDES
頁的結果如下圖所示,File Header
和File Trailer
是通用的頁結構,重點是里面存的記錄,存儲的是一個一個的XDES Entry
,一個XDES Entry
對應一個區,每個組中有一個XDES
頁,而一個XDES Entry
對應一個區。頁中有256條記錄,對應着組中的256個區。並且每個XDES
頁的位置是固定的,因為每個區的大小是固定的,所以我們可以快速定位到每個組的第一個區的開頭位置,也就可以快速定位到XDES
頁了。
_________________________________
| File Header |
|---------------------------------|
| |
|---------------------------------|
| XDES Entry 0 |
| XDES Entry 1 |
| XDES Entry 2 |
| ... |
| XDES Entry 255 |
|---------------------------------|
| Emptry Space |
|---------------------------------|
| File Trailer |
---------------------------------
2.1 XDES Entry的結構
這里先說段
,為了減少隨機IO,在進行數據存放時,Mysql希望將一類數據盡量存放在一起。我們的一條一條數據是以B+樹的方式存儲的,我沒在進行數據查找的時候,如果是索引查找,那就會從索引的根節點開始向下找,B+數種的節點分為葉子結點和非葉子結點,如果是索引查找的話,我們是先從非葉子結點查找,然后找到對應的葉子結點,如果是全部掃描的話,我們就需要直接從葉子結點的第一個節點開始查找。所以為了較少隨機IO,我們索引中頁面鏈表(B+樹中同一層的節點也就是頁是通過鏈表的方式連接的)中相鄰頁的的物理位置盡量相連。段就可以實現將葉子結點和非葉子結點分開存儲,因為段中存儲的是一個或多個區,我們的一個索引或生成兩個段進行存儲,一個存所有葉子結點,另一個存所有非葉子節點。
Segment ID
表示對應的區所屬的段的ID,只有該區已經分配給某個段,這個字段的值才有意義。因為還有隸屬於表空間的區,他們不屬於任何段。List Node
用來與其他區建立連接,形成鏈表。State
區的狀態,FREE
表示已經分配空間,但是為使用的區,FREE_FRAG
有空閑空間的碎片區FULL_FRAG
沒有空閑空間的碎片區FSEG
屬於某個段的區Page State Bitmap
一共128bit,沒2位對應一個頁,如果第一位是1,表示頁空閑
_________________________________
| Segment ID |
|---------------------------------| _______________________
| | | Prev Node Page Number |
| | -----------------------
| | | Prev Node Offset |
| List Node |---> -----------------------
| | | Next Node Paage Number|
| | -----------------------
| | | Next Node Offset |
|---------------------------------| -----------------------
| State | List Node結構
|---------------------------------|
| Page State Bitmap |
---------------------------------
XDES Entry結構
3. FSP_HDR類型頁
前面我們說了可以將FSP_HDR
看作是XDES
,因為他們都存儲了XDES Entry
,但是相比於XDES
,前者存儲了一些表空間的相關屬性。多了一個File Space Header
,由於FSP_HDR
在只存在於表空間中第一個組的第一區中的第一個頁中,所以一個表空間只有一個File Space Header
。
Space ID
表空間IDSize
當前表空間擁有的頁面數量FREE Limit
尚未被初始化的最小頁號Space Flags
表空間中一些占用存儲空間比較小的屬性FRAG_N_USED
FREE_FRAG鏈表中已經使用的頁面數量List Base Node for FREE List
FREE鏈表的基節點List Base Node for FREE_FRAG list
FREE_FRAG鏈表的基節點List Base Node for FULL_FRAG list
FULL_FRAG鏈表的基節點Next Unused Segment ID
當前表空間中下一個未使用的Segment IDList Base Node for SEG_INODES_FULL
SEG_INODES_FULL鏈表的基節點List Base Node for SEG_INODES_FREE
SEG_INODES_FREE鏈表的基節點
重點是5個基節點
_________________________________ ___________________________________
| File Header | | Space ID |
|---------------------------------| |-----------------------------------|
| File Space Header | -----> | Not Used |
|---------------------------------| |-----------------------------------|
| XDES Entry 0 | | Size |
| XDES Entry 1 | |-----------------------------------|
| XDES Entry 2 | | FREE Limit |
| ... | |-----------------------------------|
| XDES Entry 255 | | Space Flags |
|---------------------------------| |-----------------------------------|
| Emptry Space | | FRAG_N_USED |
|---------------------------------| |-----------------------------------|
| File Trailer | | List Base Node for FREE List |
--------------------------------- |-----------------------------------|
| List Base Node for FREE_FRAG list |
|-----------------------------------|
| List Base Node for FULL_FRAG list |
|-----------------------------------|
| Next Unused Segment ID |
|-----------------------------------|
|List Base Node for SEG_INODES_FULL |
|-----------------------------------|
|List Base Node for SEG_INODES_FREE |
-----------------------------------
4. INODE類型頁
INODE
類型的頁中存儲的是記錄是INODE Entry
,一個INODE Entry
對應一個段。我們還是按照慣例看看他的結構,可以看出來,他和我們前面說的XDES
頁的結構很像。右邊的List Node
結構也是用來形成鏈表的。
_________________________________
| File Header |
|---------------------------------| _______________________
| List Node for INODE Page List | --->| Prev Node Page Number |
|---------------------------------| -----------------------
| INODE Entry 0 | | Prev Node Offset |
|---------------------------------| -----------------------
| ... | | Next Node Paage Number|
|---------------------------------| -----------------------
| INODE Entry 84 | | Next Node Offset |
|---------------------------------| -----------------------
| Empty Space | List Node結構
|---------------------------------|
| File Trailer |
---------------------------------
INODE結構
4.1 INODE Entry結構
Segment ID
記錄的是段IDNOT_FULL_N_USED
NOT_FULL List中已經使用了多少個頁面List Base Node for FREE List
FREE鏈表的基節點List Base Node for NOT_FULL List
NOT_FULL鏈表的基節點List Base Node for FULL List
FULL鏈表的基節點Magic Number
標記該INODE Entry是否已經初始化Fragment Array Entry
對應着碎片區中一個一個零散頁
重點是這個鏈表,鏈表中的節點是段中的一個一個區,通過區的存儲空間占用情況,將區分為了三類,方便進行數據的存儲,也提高了效率。比如你要存儲非葉子結點頁,就只需要找到NOT_FULL
鏈表的基節點,然后存儲到對應的區就行。避免遍歷不必要的FREE和FULL鏈。
_________________________________
| Segment ID |
|---------------------------------|
| NOT_FULL_N_USED |
|---------------------------------|
| List Base Node for FREE List |
|---------------------------------|
| List Base Node for NOT_FULL List|
|---------------------------------|
| List Base Node for FULL list |
|---------------------------------|
| Magic Number |
|---------------------------------|
| Fragment Array Entry 0 |
|---------------------------------|
| ... |
|---------------------------------|
| Fragment Array Entry 31 |
---------------------------------
INODE Entry結構
到此我們已經把三個重要的頁類型的基本結構都已經介紹了,我們可以看出來,所有的數據依舊是以頁為最小單位進行存儲的,只不過不同的頁分擔着不同的責任(記錄着不同的內容),這里重復最多的是XDES
類型的頁,這里FSP_HDR
在一個表空間中只會有一個。然后就是對數據進行分類了,這里使用的是鏈表,通過鏈表的方式將將同類型(這里的類型其實說的是區或段的狀態,看一下上面5個鏈表的名稱你應該可以初步看出一些區別來)的數據連接在一起,然后在FSP_HDR
中記錄鏈表的基節點。
5. 碎片區
當我們向表空間中插入一條數據時(假設只有主鍵,沒有其他索引),按照上面我們說的,此時就會生成兩個段(一個段存放非葉子結點,另一個段存葉子節點),一個段里面要有一個區,所以一條數據會占2MB,顯然,這樣很浪費內存。所以為了解決這個問題,Mysql提出了碎片區
,碎片區直屬於表空間,當剛開始向表中插入數據時,段時從某個碎片區中以單個頁為單位分配存儲空間,當某個段占用了碎片區中32個頁面后,就會以完整的區為單位分配存儲空間(並不會將原來存在碎片區中的復制到新申請的完整的區中)。所以段
是一些完整的區和一些碎片區中的頁面組成的。
5.1 區的分類
區分為四類
FREE
空閑的區,只分配了存儲空間,但是沒使用FREE_FRAG
有剩余空閑頁面的碎片區FULL_FRAG
沒有剩余空閑頁面的碎片區FSEG
附屬於某個段的區
5.1.1 FREE、FREE_FRAG、FULL_FRAG
表空間中有多個空閑區,我們知道區對應的結構是XDES Entry
,為了區分不同類型的區,使用了鏈表將同類型的區連接起來,FREE類型的區對應的鏈表就是上面我們說的FREE List
,他對應的基節點在FSP_HDR
中的List Base Node for FREE List
,也就是說,如果我們的段或者碎片區需要空閑區了,就會從FREE List
去取,先從FSP_HDR
頁中找到List Base Node for FREE List
基節點,然后取一個節點(區),將他的狀態從FREE
改為FREE_FRAG
(以碎片區為例)后放到FREE_FRAG List
鏈表上去,然后分配頁空間,寫數據,等到碎片區滿了之后,將該碎片區的State
從FREE_FRAG
改為FULL_FRAG
后放到FULL_FRAG List
鏈表上去。
6. SEG_INODES_FULL List 和 SEG_INODES_FREE List
其實與上面的3個鏈表很相似,先說一下這兩個鏈表存的什么:
SEG_INODES_FULL List
中存放的是沒有空閑空間的INODE頁面,也就是不能存儲更多的INODE Entry了SEG_INODES_FREE List
中存放的是有空閑空間的INODE頁面
當SEG_INODES_FREE
鏈表不為空時,存儲一個INODE Entry
只需要從鏈表中獲取一個有空閑空間的INODE
頁面,然后存進入(如果滿了,就將當前節點放到SEG_INODES_FULL
鏈表中)。如果SEG_INODES_FREE
鏈表為空,就要從表空間下的FREE_FRAG
鏈表中申請一個頁面,並將該頁面的類型修改為INODE,把該頁面放到SEG_INODES_FREE
鏈表中。
7. 表空間中查找數據
以使用主鍵索引查找數據為例,為了進行數據查找,我們需要先找到該索引的非葉子結點,然后找到葉子節點,就可以找到對應的數據了。所以在前面的過程中,我們首先需要知道的是該主鍵索引在哪個表空間,接下來我們需要找到對應的段,但是我們是不能直接找到段的,我們需要先找到管理段的INODE
頁面(也就是葉號),然后找到對應INODE Entry
,也就是我們的段。所以我們查找對應的數據,至少需要知道表空間ID
、INODE頁號
、INODE Entry
的偏移位置。
8. 總結
到這里為止,表空間為了進行數據管理,引入了區
、組
、段
這些概念,使用XDES
管理區(一個XDES
頁對應一個組);使用INOD
管理段,然后為了提高空間利用率和效率,引入了碎片區,使用存儲空間的空閑狀態將同類數據再次進行區分。1)將(隸屬於表空間和隸屬於段的)區分為了3種狀態,2)將INODE
頁分為了兩種狀態(有空閑空間和沒有空閑空間)。其實整體看來並不復雜,依舊是使用最基本的頁進行數據存儲,對不同的概念使用不同的頁,再次基礎上,增加了鏈表,目的是為了講數據進一步分類,方便進行數據查找。
// TODO 有問題, 前面說的是當某個段占用了32個碎片區頁面,也就是說放碎片區頁中存的是數據頁(級別小於段),當時后面卻說存INODE Entry時,INODE空間不夠時,回去表空間下的FREE_FRAG中區申請一個頁面作為INODE使用一個INODE存放的是多個段呀()