因爲 MySQL 的真實數據是存儲在磁盤, 所以在讀寫數據是會涉及磁盤 IO, 爲了更高效率的讀取, MySQL 設計頁結構, 每次交互以頁爲單位讀取到內存. 頁的大小通常爲 16KBbash
一個數據頁能夠被大體劃分爲7個部分優化
File Header,表示頁的一些通用信息,佔固定的38字節。 Page Header,表示數據頁專有的一些信息,佔固定的56個字節。 Infimum + Supremum,兩個虛擬的僞記錄,分別表示頁中的最小和最大記錄,佔固定的26個字節。 User Records:真實存儲咱們插入的記錄的部分,大小不固定。 Free Space:頁中還沒有使用的部分,大小不肯定。 Page Directory:頁中的某些記錄相對位置,也就是各個槽在頁面中的地址偏移量,大小不固定,插入的記錄越多,這個部分佔用的空間越多。 File Trailer:用於檢驗頁是否完整的部分,佔用固定的8個字節。spa
上述的行格式即爲 User Records的一部分設計
新生成的頁並無 User Records, 當插入數據時, 會從 Free Space 申請, 若是 Free Space 的空間所有被替換掉, 則說明該頁滿了, 須要申請新的頁3d
從 select * from table wher id = 1 講起 比較笨的方式會從最小記錄開始遍歷往下找, 知道找到 next_record = 0指針
MySQL 的優化點在於新增了一個目錄, 利用二分查找優化code
- 經過二分法肯定該記錄所在的槽(實際是找到肯定的槽, 遍歷該槽的上一個槽或者當前槽)
- 經過記錄的next_record屬性遍歷該槽所在的組中的各個記錄
由於把16條記錄的所有信息都畫在一張圖裏太佔地方,讓人眼花繚亂的,因此只保留了用戶記錄頭信息中的n_owned和next_record屬性,也省略了各個記錄之間的箭頭,我沒畫不等於沒有啊!如今看怎麼從這個頁目錄中查找記錄。由於各個槽表明的記錄的主鍵值都是從小到大排序的,因此咱們可使用所謂的二分法來進行快速查找。5個槽的編號分別是:0、一、二、三、4,因此初始狀況下最低的槽就是low=0,最高的槽就是high=4。比方說咱們想找主鍵值爲6的記錄,過程是這樣的:
計算中間槽的位置:(0+4)/2=2,因此查看槽2對應記錄的主鍵值爲8,又由於8 > 6,因此設置high=2,low保持不變。
從新計算中間槽的位置:(0+2)/2=1,因此查看槽1對應的主鍵值爲4,又由於4 < 6,因此設置low=1,high保持不變。
由於high - low的值爲1,因此肯定主鍵值爲6的記錄在槽2對應的組中。此刻咱們須要找到槽2中主鍵值最小的那條記錄,而後沿着單向鏈表遍歷槽2中的記錄。可是咱們前邊又說過,每一個槽對應的記錄都是該組中主鍵值最大的記錄,這裏槽2對應的記錄是主鍵值爲8的記錄,怎麼定位一個組中最小的記錄呢?別忘了各個槽都是挨着的,咱們能夠很輕易的拿到槽1對應的記錄(主鍵值爲4),該條記錄的下一條記錄就是槽2中主鍵值最小的記錄,該記錄的主鍵值爲5。因此咱們能夠從這條主鍵值爲5的記錄出發,遍歷槽2中的各條記錄,直到找到主鍵值爲6的那條記錄便可。因爲一個組中包含的記錄條數只能是1~8條,因此遍歷一個組中的記錄的代價是很小的。
因此在一個數據頁中查找指定主鍵值的記錄的過程分爲兩步:
經過二分法肯定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。
經過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。
複製代碼
行格式主要分爲四種類型Compact
、
Redundant、
Dynamic和
Compressed. 主要理解 Compactcdn
行格式主要分爲記錄的額外信息, 記錄的真實數據blog
變長字段長度列表存儲的是變長類型的真實數據的佔用字節數(逆序).排序
若是該表沒有變長類型, 則無變長字段長度列表
變長字段長度列表只保存 NOT NULL列, 若是該列容許爲 NULL, 則不會保存
舉例: 字段 0 int 0, 字段1 vachar(10) NOT NULL 'A', 字段2 vachar(10) default NULL 'B', 字段3 vachar(10) NOT NULL 'AA'
變長字段長度列表: 02 01
可經過 變長字段反推字段的長度
複製代碼
NULL 值列表只統計哪些字段容許爲 NULL的值狀態(逆序)
若是該表沒有容許 NULL 列, 則無 NULL 值列表
MySQL規定NULL值列表必須用整數個字節的位表示,若是使用的二進制位個數不是整數個字節,則在字節的高位補0
二進制位的值爲1時,表明該列的值爲NULL 二進制位的值爲0時,表明該列的值不爲NULL
舉例: 字段 0 vachar(10) NOT NULL 'A', 字段1 vachar(10) default NULL 'B', 字段2 vachar(10) default NULL NULL, 字段3 vachar(10) default NULL NULL
逆序後: 字段3 - 1, 字段2 - 1, 字段1 - 0
NULL 值列表: 00000110
可經過 NOT NULL 字段反推哪些字段值爲 NULL, 哪些字段值不爲 NULL
複製代碼
這一部分牽扯的內容較多
大體能夠看一下刪除標識(刪除是並不是真正刪除, 只是修改表示), 下一條記錄位置(B+樹特性)
除了這些詳細信息, 還會有 MySQL 自動添加的隱藏列
實際上這幾個列的真正名稱實際上是:DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR, 爲了美觀才寫成了row_id、transaction_id和roll_pointer
row_id: 有主鍵使用主鍵, 沒有主鍵有惟一鍵使用惟一鍵, 沒有則自動生成
transaction_id: 事務 ID
roll_pointer: 回滾指針
由於一個頁佔用 16KB, 16 * 1024 = 16384 字節, 而 varchar 最多能夠佔用65535, 不包括隱藏列和記錄頭信息
可是在咱們使用 varchar 時, 會設置變長字段長度列表和 NULL 值列表
對於 NOT NULL, 只能使用65533字節 (兩個字節用來表示長度) 對於非 NOT NULL, 只用使用 65532 字節 (兩個字節用來表示長度, 一個字節用來表示 NULL 標識)
對於行溢出的狀況, 真實的數據只用存儲前 768 字節, 後面放的是溢出頁地址
複製代碼
綜上所述, 咱們知道在數據的佔用字節數超過某個閾值就會發生行溢出, 那麼行溢出的計算方式分析以下
ps: 截圖來源於MySQL 是怎樣運行的:從根兒上理解 MySQL(小孩子 4919)
這個屬性標識該條記錄是否已刪除
若是 MySQL 設計刪除一條則執行磁盤刪除, 會致使磁盤IO增長, MySQL 爲了優化, 使用一個標識表示該記錄已被刪除, 若是有新的記錄來, 會覆蓋該條記錄 原理: 刪除數據時, 標識該記錄已刪除, 而且放入垃圾鏈表, 新的記錄插入時, 先從垃圾鏈表中替換
若是想要優化空間, 則執行 optimize table 'name' 便可
MySQL 是怎樣運行的:從根兒上理解 MySQL(記錄結構) MySQL 是怎樣運行的:從根兒上理解 MySQL(數據頁結構)