MySQL 是怎樣運行的 - InnoDB數據頁結構

 數據頁結構的快速瀏覽

數據頁表明的這塊16KB大小的存儲空間能夠被劃分爲多個部分,不一樣部分有不一樣的功能,各個部分如圖所示:mysql

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

一個InnoDB數據頁的存儲空間大體被劃分紅了7個部分算法

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

1、記錄在頁中的存放

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

  • 存儲的記錄會按照咱們指定的行格式存儲到User Records部分
  • 一開始生成頁的時候,其實並無User Records這個部分,每當咱們插入一條記錄,都會從Free Space部分,也就是還沒有使用的存儲空間中申請一個記錄大小的空間劃分到User Records部分,
  • 當Free Space部分的空間所有被User Records部分替代掉以後,也就意味着這個頁使用完了,若是還有新的記錄插入的話,就須要去申請新的頁了

一、記錄頭信息的祕密sql

咱們先建立一個表:ide

mysql> CREATE TABLE page_demo(
    ->     c1 INT,
    ->     c2 INT,
    ->     c3 VARCHAR(10000),
    ->     PRIMARY KEY (c1)
    -> ) CHARSET=ascii ROW_FORMAT=Compact;
Query OK, 0 rows affected (0.03 sec)

c1和c2列是用來存儲整數的,c3列是用來存儲字符串的spa

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

在page_demo表的行格式演示圖中畫出有關的頭信息屬性以及c一、c二、c3列的信息(其餘信息沒畫不表明它們不存在啊,只是爲了理解上的方便在圖中省略了~),簡化後的行格式示意圖就是這樣:3d

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

繼續插入數據日誌

mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd'); 
Query OK, 4 rows affected (0.00 sec) 
Records: 4 Duplicates: 0 Warnings: 0

如圖所示code

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

  • delete_mask 這個屬性標記着當前記錄是否被刪除,佔用1個二進制位,值爲0的時候表明記錄並無被刪除,值爲1的時候表明記錄被刪除掉了。 並不會當即刪除,而是會將全部被刪除掉的記錄都會組成一個所謂的垃圾鏈表,在這個鏈表中的記錄佔用的空間稱之爲所謂的可重用空間,以後若是有新記錄插入到表中的話,可能把這些被刪除的記錄佔用的存儲空間覆蓋掉。
  • min_rec_mask B+樹的每層非葉子節點中的最小記錄都會添加該標記
  • n_owned 這個暫時保密,稍後它就是主角~
  • heap_no 這個屬性表示當前記錄在本頁中的位置,從圖中能夠看出來,咱們插入的4條記錄在本頁中的位置分別是:二、三、四、5。少了 0 和 1 mysql會每一個頁裏邊兒加了兩個記錄,因爲這兩個記錄並非咱們本身插入的,因此有時候也稱爲僞記錄或者虛擬記錄。這兩個僞記錄一個表明最小記錄,一個表明最大記錄 記錄是可用比大小的,比較的是主鍵 無論咱們向頁中插入了多少本身的記錄,Mysql都會自動生成兩條僞記錄分別爲最小記錄與最大記錄。這兩條記錄的構造十分簡單,都是由5字節大小的記錄頭信息和8字節大小的一個固定的部分組成的,如圖所示

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

因爲這兩條記錄不是咱們本身定義的記錄,因此它們並不存放在頁的User Records部分,他們被單獨放在一個稱爲Infimum + Supremum的部分,如圖所示:blog

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

其表明的 heap_no 分別是 0 和 1索引

record_type 這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹葉節點記錄,2表示最小記錄,3表示最大記錄。從圖中咱們也能夠看出來,咱們本身插入的記錄就是普通記錄,它們的record_type值都是0,而最小記錄和最大記錄的record_type值分別爲2和3

next_record 它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量 最大記錄的next_record的值爲0

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

若是從中刪除掉一條記錄,這個鏈表也是會跟着變化的,好比咱們把第2條記錄刪掉:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

從圖中能夠看出來,刪除第2條記錄先後主要發生了這些變化: 第2條記錄並無從存儲空間中移除,而是把該條記錄的delete_mask值設置爲1。 第2條記錄的next_record值變爲了0,意味着該記錄沒有下一條記錄了。 第1條記錄的next_record指向了第3條記錄。 還有一點你可能忽略了,就是最大記錄的n_owned值從5變成了4,關於這一點的變化咱們稍後會詳細說明的。

若是咱們再次把這條記錄插入到表中的話

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

從圖中能夠看到,InnoDB並無由於新記錄的插入而爲它申請新的存儲空間,而是直接複用了原來被刪除記錄的存儲空間。

2、Page Directory(頁目錄)

如今咱們瞭解了記錄在頁中按照主鍵值由小到大順序串聯成一個單鏈表,那若是咱們想根據主鍵值查找頁中的某條記錄該咋辦呢

SELECT * FROM page_demo WHERE c1 = 3;

MySQL 的查詢流程 將全部正常的記錄(包括最大和最小的記錄,不包括標記爲已刪除的記錄)劃分爲幾個組。 每一個組的最後一條記錄(也就是組內最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內共有幾條記錄。 將每一個組的最後一條記錄的地址偏移量單獨提取出來按順序存儲到靠近頁的尾部的地方,這個地方就是所謂的Page Directory,也就是頁目錄(此時應該返回頭看看頁面各個部分地圖)。頁面目錄中的這些地址偏移量被稱爲槽(英文名:Slot),因此這個頁面目錄就是由槽組成的。

比方說如今的page_demo表中正常的記錄共有6條,InnoDB會把它們分紅兩組,第一組中只有一個最小記錄,第二組中是剩餘的5條記錄,看下邊的示意圖:

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

注意最小和最大記錄的頭信息中的n_owned屬性 最小記錄的n_owned值爲1,這就表明着以最小記錄結尾的這個分組中只有1條記錄,也就是最小記錄自己。 最大記錄的n_owned值爲5,這就表明着以最大記錄結尾的這個分組中只有5條記錄,包括最大記錄自己還有咱們本身插入的4條記錄。

對於最小記錄所在的分組只能有 1 條記錄,最大記錄所在的分組擁有的記錄條數只能在 1~8 條件之間,剩下的分組中記錄的條數範圍只能在是 4~8 條之間。

因此 最小記錄的 n_owned 是 1, 當前最大記錄的 n_owned 是 5

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

以後每插入一條記錄,都會從頁目錄中找到主鍵值比本記錄的主鍵值大而且差值最小的槽,而後把該槽對應的記錄的n_owned值加1,表示本組內又添加了一條記錄,直到該組中的記錄數等於8個。

在一個組中的記錄數等於8個後再插入一條記錄時,會將組中的記錄拆分紅兩個組,一個組中4條記錄,另外一個5條記錄。這個過程會在頁目錄中新增一個槽來記錄這個新增分組中最大的那條記錄的偏移量。

再次添加12條數據

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

比方說咱們想找主鍵值爲6的記錄

  1. 計算中間槽的位置:(0+4)/2=2,因此查看槽2對應記錄的主鍵值爲8,又由於8 > 6,因此設置high=2,low保持不變。
  2. 從新計算中間槽的位置:(0+2)/2=1,因此查看槽1對應的主鍵值爲4,又由於4 < 6,因此設置low=1,high保持不變。
  3. 由於high - low的值爲1,因此肯定主鍵值爲6的記錄在槽2對應的組中。此刻咱們須要找到槽2中主鍵值最小的那條記錄,而後沿着單向鏈表遍歷槽2中的記錄。
  4. 咱們能夠拿到槽1對應的記錄(主鍵值爲4),該條記錄的下一條記錄就是槽2中主鍵值最小的記錄,該記錄的主鍵值爲5。因此咱們能夠從這條主鍵值爲5的記錄出發,遍歷槽2中的各條記錄,直到找到主鍵值爲6的那條記錄便可。因爲一個組中包含的記錄條數只能是1~8條,因此遍歷一個組中的記錄的代價是很小的。

因此在一個數據頁中查找指定主鍵值的記錄的過程分爲兩步:

  1. 經過二分法肯定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。
  2. 經過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。
3、Page Header(頁面頭部)

爲了能獲得一個數據頁中存儲的記錄的狀態信息,好比本頁中已經存儲了多少條記錄,第一條記錄的地址是什麼,頁目錄中存儲了多少個槽等等,特地在頁中定義了一個叫Page Header的部分,它是頁結構的第二部分,這個部分佔用固定的56個字節,專門存儲各類狀態信息,具體各個字節都是幹嗎的看下錶:

名稱 佔用空間大小 描述
PAGE_N_DIR_SLOTS 2字節 在頁目錄中的槽數量
PAGE_HEAP_TOP 2字節 還未使用的空間最小地址,也就是說從該地址以後就是Free Space
PAGE_N_HEAP 2字節 本頁中的記錄的數量(包括最小和最大記錄以及標記爲刪除的記錄)
PAGE_FREE 2字節 第一個已經標記爲刪除的記錄地址(各個已刪除的記錄經過next_record也會組成一個單鏈表,這個單鏈表中的記錄能夠被從新利用)
PAGE_GARBAGE 2字節 已刪除記錄佔用的字節數
PAGE_LAST_INSERT 2字節 最後插入記錄的位置
PAGE_DIRECTION 2字節 記錄插入的方向
PAGE_N_DIRECTION 2字節 一個方向連續插入的記錄數量
PAGE_N_RECS 2字節 該頁中記錄的數量(不包括最小和最大記錄以及被標記爲刪除的記錄)
PAGE_MAX_TRX_ID 8字節 修改當前頁的最大事務ID,該值僅在二級索引中定義
PAGE_LEVEL 2字節 當前頁在B+樹中所處的層級
PAGE_INDEX_ID 8字節 索引ID,表示當前頁屬於哪一個索引
PAGE_BTR_SEG_LEAF 10字節 B+樹葉子段的頭部信息,僅在B+樹的Root頁定義
PAGE_BTR_SEG_TOP 10字節 B+樹非葉子段的頭部信息,僅在B+樹的Root頁定義
4、File Header(文件頭部)

存儲頁的通用信息,比方說這個頁的編號是多少,它的上一個頁、下一個頁是誰啦吧啦吧啦~ 這個部分佔用固定的38個字節,是由下邊這些內容組成的:

名稱 佔用空間大小 描述
FIL_PAGE_SPACE_OR_CHKSUM 4字節 頁的校驗和(checksum值)
FIL_PAGE_OFFSET 4字節 頁號
FIL_PAGE_PREV 4字節 上一個頁的頁號
FIL_PAGE_NEXT 4字節 下一個頁的頁號
FIL_PAGE_LSN 8字節 頁面被最後修改時對應的日誌序列位置(英文名是:Log Sequence Number)
FIL_PAGE_TYPE 2字節 該頁的類型
FIL_PAGE_FILE_FLUSH_LSN 8字節 僅在系統表空間的一個頁中定義,表明文件至少被刷新到了對應的LSN值
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4字節 頁屬於哪一個表空間

對照着這個表格,咱們看幾個目前比較重要的部分:

  • FIL_PAGE_SPACE_OR_CHKSUM 這個表明當前頁面的校驗和(checksum)。啥是個校驗和?就是對於一個很長很長的字節串來講,咱們會經過某種算法來計算一個比較短的值來表明這個很長的字節串,這個比較短的值就稱爲校驗和。這樣在比較兩個很長的字節串以前先比較這兩個長字節串的校驗和,若是校驗和都不同兩個長字節串確定是不一樣的,因此省去了直接比較兩個比較長的字節串的時間損耗。
  • FIL_PAGE_OFFSET 每個頁都有一個單獨的頁號,就跟你的身份證號碼同樣,InnoDB經過頁號來能夠惟必定位一個頁。
  • FIL_PAGE_TYPE 這個表明當前頁的類型,咱們前邊說過,InnoDB爲了避免同的目的而把頁分爲不一樣的類型,咱們上邊介紹的其實都是存儲記錄的數據頁,其實還有不少別的類型的頁,具體以下表: 類型名稱十六進制描述FIL_PAGE_TYPE_ALLOCATED0x0000最新分配,還沒使用FIL_PAGE_UNDO_LOG0x0002Undo日誌頁FIL_PAGE_INODE0x0003段信息節點FIL_PAGE_IBUF_FREE_LIST0x0004Insert Buffer空閒列表FIL_PAGE_IBUF_BITMAP0x0005Insert Buffer位圖FIL_PAGE_TYPE_SYS0x0006系統頁FIL_PAGE_TYPE_TRX_SYS0x0007事務系統數據FIL_PAGE_TYPE_FSP_HDR0x0008表空間頭部信息FIL_PAGE_TYPE_XDES0x0009擴展描述頁FIL_PAGE_TYPE_BLOB0x000A溢出頁FIL_PAGE_INDEX0x45BF索引頁,也就是咱們所說的數據頁 咱們存放記錄的數據頁的類型實際上是FIL_PAGE_INDEX,也就是所謂的索引頁。至於啥是個索引,且聽下回分解~
  • FIL_PAGE_PREV和FIL_PAGE_NEXT

watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

5、總結

一個數據頁能夠被大體劃分爲7個部分,分別是 File Header,表示頁的一些通用信息,佔固定的38字節。 Page Header,表示數據頁專有的一些信息,佔固定的56個字節。 Infimum + Supremum,兩個虛擬的僞記錄,分別表示頁中的最小和最大記錄,佔固定的26個字節。 User Records:真實存儲咱們插入的記錄的部分,大小不固定。 Free Space:頁中還沒有使用的部分,大小不肯定。 Page Directory:頁中的某些記錄相對位置,也就是各個槽在頁面中的地址偏移量,大小不固定,插入的記錄越多,這個部分佔用的空間越多。 File Trailer:用於檢驗頁是否完整的部分,佔用固定的8個字節。

相關文章
相關標籤/搜索