首先要從Innodb怎麼看待磁碟物理空間說起
一塊原生的(Raw)物理磁碟,可以把他看成一個位元組一個位元組單元組成的物理儲存介質
如果要在這塊原生物理空間中插入一條記錄,不能單單隻插入資料,還需要插入一些管理記錄的資訊,這些管理資訊被稱為記錄頭,這裡假設是5位元組(compact型別記錄確實記錄頭佔用5位元組,簡單通俗起見,可以忽略這段括號內的解釋)
然後在記錄頭後面插入列,假如要插入的記錄的各個列是:
其中 num 是主鍵 (int型別)
name 是 varchar 型別的
sex 是 int 型別的
那麼按照 int 佔用 4 位元組,不超過長度閾值(8000+)的 varchar 按照實際長度 ('abc' 佔 3 位元組) 儲存的規則 把這條記錄填充到 Raw物理空間中
問題來了:管理資訊有什麼用呢?
在儲存組織上最重要的用處是找到下一條記錄
不能直接找到下一條記錄嗎?不能。假如我已經知道了第一條記錄資料的開頭部分,也就是上圖第一個藍色方格(A)的編號
現在插入多一條記錄:
如何才能獲取第二條記錄的第一個藍色方格(B)編號? 這個藍色方格(B)就是第二條記錄資料部分的起始地址
可不可以用 A 的編號加上偏移量得到呢?
B的編號 = A 的編號 + 8 + 6 ? 實際上不行,因為黃色部分的 name 是不定長的,偏移量也不一定,就如下圖,偏移量隨著不定長欄位長度改變而改變
管理資訊可以記錄下一條記錄編號的偏移量
形成一種連結串列管理方式:每條記錄的資料部分可以看成一個結點
把他抽象一下就得到了 下圖這種方式
但是為什麼會有倒著指的情況存在呢?
(圖 A )
假如刪除第二條記錄:
最後,被刪除的第二條記錄被移出了上面提到的,儲存有用記錄的連結串列
如果把整個物理空間擴大,找到其他同樣也是被刪除的記錄。實際上這些被刪除的記錄,會被標記為空閒狀態(管理資訊中有標誌位)
然後採用實際有用的資料相同的連結方式,連線成一條連結串列,稱為空閒連結串列
下次再插入一條資料的時候,如果從空閒連結串列中找到了符合要插入記錄大小的空閒空間(上圖白色部分)就會把這一部分分配出去
下圖綠色的部分是新記錄,當然新記錄不一定會佔滿之前留下的空閒空間
藍色的那條指向,是一條倒著的指向,也就可以解釋之前圖 A 上為什麼有倒著的連結串列指向了
所以,一個物理上的資料中的記錄是邏輯上按照連結串列順序連線起來的,並且是按照主鍵遞增的順序連線成一條單連結串列
之前說過,4位元組的num是主鍵,如果刪除的是 主鍵 = 2 的記錄,那麼最後物理上看起來是這樣的:
新增加的記錄,主鍵是 7,佔用了被刪掉的記錄(主鍵 = 2)的位置(不一定能佔滿,上圖是假設佔滿了)
之所以說這條連結串列是邏輯上主鍵遞增的,是因為在物理上這條連結串列並不是主鍵遞增,上圖最明顯的不是遞增特點表現在7插在了1和3之間
我們把下圖的這一塊稱為一個資料頁,資料頁是 Innodb 磁碟儲存管理的最小單位。當然,實際上資料頁不會像下圖這樣才幾條記錄,下圖只是一個迷你版的表示
預設資料頁真正大小一般是16 KB , 真正看起來可能是密密麻麻一大片:
每一頁都持有上一頁和下一頁在物理檔案中的編號(地址)頁和頁之間可以串起來:
(實際上是頁結構中的File Header部分儲存了上一頁/下一頁在表空間檔案中的偏移量(編號)
如果一個獨立的表空間檔案(.idb) 的大小是1GB,每個頁的大小是 16KB, 那麼總共有1GB / 16KB = 65536個頁
下文均討論聚簇索引
(下文的B+樹都是簡化的,實際上B樹節點的度不會那麼小)
這些頁都是 Innodb 的 B+ 樹儲存結構中的 資料頁節點,也就是葉子節點
可以加上非葉子節點(索引節點),讓他成為一顆完整的 B+ 樹:
現在大概有一個儲存結構的大體認識了,來解決一個比較深入的問題:上圖的索引節點是什麼,怎麼通過這些索引節點做查詢
首先了解表的儲存結構:如果使用獨立表空間,表的索引和記錄將會儲存在一個獨立的idb檔案中
idb檔案可以按照規定好的資料頁大小切分成若干頁
每個資料頁都有自己獨特的頁號,其實就是頁的偏移量,可以唯一表示一個資料頁
需要注意的是物理頁的物理順序和邏輯順序可能不一樣,比如:
資料頁無需的結果可能是這樣的:
聚簇索引頁的記錄只是簡單的把頁的最小主鍵值和頁的頁號關聯起來
聚簇索引頁的上一層索引頁(邏輯上)也只是簡單的記錄下層索引頁最小主鍵值和頁號的對映
當然,Innodb的B+樹的扇出度 (fan out)是很高的,像上圖這樣少量的資料頁一般只有一層索引節點,且只有一個。
回到一開始我們的目的,假如我要查詢 主鍵 = 25 的記錄
找到資料頁4,但是要怎麼找記錄呢?innodb會把這片資料頁載入入記憶體,根據這個資料頁的page Directory進行二分查詢
Page Directory 其實只是一堆偏移量而已
在上面的頁中,如果我要查詢主鍵 = 3 的記錄,那麼先設定左指標 l = 第一個page directory 項的位置,右指標 r = 最後一個 page directory項的位置
根據二分查詢,求出中間的位置,然後把中間的 page directory 項讀出來,發現記錄的主鍵是4,比要找的 3 大,那就縮小範圍,把右指標 r 設定成剛才
算出來的中間項的位置,l 和 r 之間已經沒有沒有page director的項 了,所以從 l 指標指向的記錄開始,一條條往後讀,最多讀取其實記錄的n_owned次
讀不到就表示目標不存在,n_owned其實表示的就是當前記錄到下一個Page Directory有指向的記錄之間有多少條記錄,這些記錄的查詢都是歸當前記錄管
所以根據索引只能查到資料頁,把頁讀進記憶體在進行二分查詢,因為是在記憶體中操作,相比於索引查詢時的磁碟操作,可以忽略