從新學習Mysql數據庫3:Mysql存儲引擎與數據存儲原理

轉自:https://draveness.me/mysql-in...html

做爲一名開發人員,在平常的工做中會難以免地接觸到數據庫,不管是基於文件的 sqlite 仍是工程上使用很是普遍的 MySQL、PostgreSQL,可是一直以來也沒有對數據庫有一個很是清晰而且成體系的認知,因此最近兩個月的時間看了幾本數據庫相關的書籍而且閱讀了 MySQL 的官方文檔,但願對各位瞭解數據庫的、不瞭解數據庫的有所幫助。node

本文中對於數據庫的介紹以及研究都是在 MySQL 上進行的,若是涉及到了其餘數據庫的內容或者實現會在文中單獨指出。mysql

[](https://draveness.me/mysql-in...

不少開發者在最開始時其實都對數據庫有一個比較模糊的認識,以爲數據庫就是一堆數據的集合,可是實際卻比這複雜的多,數據庫領域中有兩個詞很是容易混淆,也就是_數據庫_和_實例_:程序員

  • 數據庫:物理操做文件系統或其餘形式文件類型的集合;
  • 實例:MySQL 數據庫由後臺線程以及一個共享內存區組成;
對於數據庫和實例的定義都來自於  MySQL 技術內幕:InnoDB 存儲引擎 一書,想要了解 InnoDB 存儲引擎的讀者能夠閱讀這本書籍。

[](https://draveness.me/mysql-in...

在 MySQL 中,實例和數據庫每每都是一一對應的,而咱們也沒法直接操做數據庫,而是要經過數據庫實例來操做數據庫文件,能夠理解爲數據庫實例是數據庫爲上層提供的一個專門用於操做的接口。面試

Database - Instance

在 Unix 上,啓動一個 MySQL 實例每每會產生兩個進程,mysqld 就是真正的數據庫服務守護進程,而 mysqld_safe 是一個用於檢查和設置 mysqld 啓動的控制程序,它負責監控 MySQL 進程的執行,當 mysqld 發生錯誤時,mysqld_safe 會對其狀態進行檢查並在合適的條件下重啓。算法

[](https://draveness.me/mysql-in... 的架構

MySQL 從第一個版本發佈到如今已經有了 20 多年的歷史,在這麼多年的發展和演變中,整個應用的體系結構變得愈來愈複雜:sql

Logical-View-of-MySQL-Architecture

最上層用於鏈接、線程處理的部分並非 MySQL 『發明』的,不少服務都有相似的組成部分;第二層中包含了大多數 MySQL 的核心服務,包括了對 SQL 的解析、分析、優化和緩存等功能,存儲過程、觸發器和視圖都是在這裏實現的;而第三層就是 MySQL 中真正負責數據的存儲和提取的存儲引擎,例如:InnoDBMyISAM 等,文中對存儲引擎的介紹都是對 InnoDB 實現的分析。數據庫

[](https://draveness.me/mysql-in...

在整個數據庫體系結構中,咱們可使用不一樣的存儲引擎來存儲數據,而絕大多數存儲引擎都以二進制的形式存儲數據;這一節會介紹 InnoDB 中對數據是如何存儲的。後端

在 InnoDB 存儲引擎中,全部的數據都被邏輯地存放在表空間中,表空間(tablespace)是存儲引擎中最高的存儲邏輯單位,在表空間的下面又包括段(segment)、區(extent)、頁(page):緩存

Tablespace-segment-extent-page-row

同一個數據庫實例的全部表空間都有相同的頁大小;默認狀況下,表空間中的頁大小都爲 16KB,固然也能夠經過改變 innodb_page_size 選項對默認大小進行修改,須要注意的是不一樣的頁大小最終也會致使區大小的不一樣:

Relation Between Page Size - Extent Size

從圖中能夠看出,在 InnoDB 存儲引擎中,一個區的大小最小爲 1MB,頁的數量最少爲 64 個。

[](https://draveness.me/mysql-in...

MySQL 使用 InnoDB 存儲表時,會將表的定義和數據索引等信息分開存儲,其中前者存儲在 .frm 文件中,後者存儲在 .ibd 文件中,這一節就會對這兩種不一樣的文件分別進行介紹。

frm-and-ibd-file

[](https://draveness.me/mysql-in... 文件

不管在 MySQL 中選擇了哪一個存儲引擎,全部的 MySQL 表都會在硬盤上建立一個 .frm 文件用來描述表的格式或者說定義;.frm 文件的格式在不一樣的平臺上都是相同的。

<pre>CREATE TABLE test_frm(    column1 CHAR(5),    column2 INTEGER);
</pre>

當咱們使用上面的代碼建立表時,會在磁盤上的 datadir 文件夾中生成一個 test_frm.frm 的文件,這個文件中就包含了表結構相關的信息:

frm-file-hex

MySQL 官方文檔中的  11.1 MySQL .frm File Format 一文對於  .frm文件格式中的二進制的內容有着很是詳細的表述,在這裏就不展開介紹了。

[](https://draveness.me/mysql-in... 文件

InnoDB 中用於存儲數據的文件總共有兩個部分,一是系統表空間文件,包括 ibdata1ibdata2 等文件,其中存儲了 InnoDB 系統信息和用戶數據庫表數據和索引,是全部表公用的。

當打開 innodb_file_per_table 選項時,.ibd 文件就是每個表獨有的表空間,文件存儲了當前表的數據和相關的索引數據。

[](https://draveness.me/mysql-in...

與現有的大多數存儲引擎同樣,InnoDB 使用頁做爲磁盤管理的最小單位;數據在 InnoDB 存儲引擎中都是按行存儲的,每一個 16KB 大小的頁中能夠存放 2-200 行的記錄。

當 InnoDB 存儲數據時,它可使用不一樣的行格式進行存儲;MySQL 5.7 版本支持如下格式的行存儲方式:

Antelope-Barracuda-Row-Format

Antelope 是 InnoDB 最開始支持的文件格式,它包含兩種行格式 Compact 和 Redundant,它最開始並無名字;Antelope 的名字是在新的文件格式 Barracuda 出現後才起的,Barracuda 的出現引入了兩種新的行格式 Compressed 和 Dynamic;InnoDB 對於文件格式都會向前兼容,而官方文檔中也對以後會出現的新文件格式預先定義好了名字:Cheetah、Dragon、Elk 等等。

兩種行記錄格式 Compact 和 Redundant 在磁盤上按照如下方式存儲:

COMPACT-And-REDUNDANT-Row-Format

Compact 和 Redundant 格式最大的不一樣就是記錄格式的第一個部分;在 Compact 中,行記錄的第一部分倒序存放了一行數據中列的長度(Length),而 Redundant 中存的是每一列的偏移量(Offset),從整體上上看,Compact 行記錄格式相比 Redundant 格式可以減小 20% 的存儲空間。

[](https://draveness.me/mysql-in...

當 InnoDB 使用 Compact 或者 Redundant 格式存儲極長的 VARCHAR 或者 BLOB 這類大對象時,咱們並不會直接將全部的內容都存放在數據頁節點中,而是將行數據中的前 768 個字節存儲在數據頁中,後面會經過偏移量指向溢出頁。

Row-Overflo

可是當咱們使用新的行記錄格式 Compressed 或者 Dynamic 時都只會在行記錄中保存 20 個字節的指針,實際的數據都會存放在溢出頁面中。

Row-Overflow-in-Barracuda

固然在實際存儲中,可能會對不一樣長度的 TEXT 和 BLOB 列進行優化,不過這就不是本文關注的重點了。

想要了解更多與 InnoDB 存儲引擎中記錄的數據格式的相關信息,能夠閱讀  InnoDB Record Structure

[](https://draveness.me/mysql-in...

頁是 InnoDB 存儲引擎管理數據的最小磁盤單位,而 B-Tree 節點就是實際存放表中數據的頁面,咱們在這裏將要介紹頁是如何組織和存儲記錄的;首先,一個 InnoDB 頁有如下七個部分:

InnoDB-B-Tree-Node

每個頁中包含了兩對 header/trailer:內部的 Page Header/Page Directory 關心的是頁的狀態信息,而 Fil Header/Fil Trailer 關心的是記錄頁的頭信息。

在頁的頭部和尾部之間就是用戶記錄和空閒空間了,每個數據頁中都包含 Infimum 和 Supremum 這兩個虛擬的記錄(能夠理解爲佔位符),Infimum 記錄是比該頁中任何主鍵值都要小的值,Supremum 是該頁中的最大值:

Infimum-Rows-Supremum

User Records 就是整個頁面中真正用於存放行記錄的部分,而 Free Space 就是空餘空間了,它是一個鏈表的數據結構,爲了保證插入和刪除的效率,整個頁面並不會按照主鍵順序對全部記錄進行排序,它會自動從左側向右尋找空白節點進行插入,行記錄在物理存儲上並非按照順序的,它們之間的順序是由 next_record 這一指針控制的。

B+ 樹在查找對應的記錄時,並不會直接從樹中找出對應的行記錄,它只能獲取記錄所在的頁,將整個頁加載到內存中,再經過 Page Directory 中存儲的稀疏索引和 n_ownednext_record 屬性取出對應的記錄,不過由於這一操做是在內存中進行的,因此一般會忽略這部分查找的耗時。

InnoDB 存儲引擎中對數據的存儲是一個很是複雜的話題,這一節中也只是對錶、行記錄以及頁面的存儲進行必定的分析和介紹,雖然做者相信這部分知識對於大部分開發者已經足夠了,可是想要真正消化這部份內容還須要不少的努力和實踐。下文是詳細分析。

innodb數據存儲詳細分析

本文主要介紹InnoDB存儲引擎的邏輯存儲結構

[](http://zhongmingmao.me/2017/0... "邏輯存儲結構")邏輯存儲結構

table-space

[](http://zhongmingmao.me/2017/0... "Tablespace")Tablespace

  1. Tablespace是InnoDB存儲引擎邏輯存儲結構的最高層全部數據都存放在Tablespace中
  2. 分類

    • System Tablespace
    • Separate Tablespace
    • General Tablespace

[](http://zhongmingmao.me/2017/0... "System Tablespace")System Tablespace

  1. System Tablespace即咱們常見的共享表空間,變量爲innodb_data_file_path,通常爲ibdata1文件
  2. 裏面存放着undo logschange bufferdoublewrite buffer等信息(後續將詳細介紹),在沒有開啓file-per-table的狀況下,還會包含全部表的索引和數據信息
  3. 沒有開啓file-per-table時存在的問題

    • 全部的表和索引都會在System Tablespace中,佔用空間會愈來愈大
    • 碎片愈來愈多(如truncate table時,佔用的磁盤空間依舊保留在System Tablespace
<pre>12345678910111213141516171819</pre> <pre>mysql>  SHOW VARIABLES LIKE 'innodb_data_file_path';+-----------------------+------------------------+  Variable_name           Value                   +-----------------------+------------------------+  innodb_data_file_path   ibdata1:12M:autoextend  +-----------------------+------------------------+1 row in set (0.01 sec)mysql>  SHOW VARIABLES LIKE '%datadir%';+---------------+-----------------+  Variable_name   Value            +---------------+-----------------+  datadir         /var/lib/mysql/  +---------------+-----------------+1 row in set (0.01 sec)mysql> system sudo ls -lh /var/lib/mysql/ibdata1[sudo] password for zhongmingmao:-rw-r----- 1 mysql mysql 76M May  6 20:00 /var/lib/mysql/ibdata1</pre>

[](http://zhongmingmao.me/2017/0... "Separate Tablespace")Separate Tablespace

  1. MySQL參考手冊中並無Separate Tablespace這個術語,這裏只爲了行文方便,表示在開啓file-per-table的狀況下,每一個表有本身獨立的表空間,變量爲innodb_file_per_table
  2. 裏面存放在每一個表的索引和數據信息,後綴通常爲.ibd
  3. 默認初始大小爲96KB
  4. 好處

    • 避免System Tablespace愈來愈大
    • 減小碎片(truncate table,操做系統會自動回收空間
<pre>123456789101112131415161718192021222324252627282930313233</pre> <pre>mysql> use testReading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -ADatabase changedmysql> show tables;+----------------+  Tables_in_test  +----------------+  t               +----------------+1 row in set (0.00 sec)mysql>  SHOW VARIABLES LIKE 'innodb_file_per_table';+-----------------------+-------+  Variable_name           Value  +-----------------------+-------+  innodb_file_per_table   ON     +-----------------------+-------+1 row in set (0.00 sec)mysql>  SHOW VARIABLES LIKE '%datadir%';                                                                                               +---------------+-----------------+  Variable_name   Value            +---------------+-----------------+  datadir         /var/lib/mysql/  +---------------+-----------------+1 row in set (0.01 sec)mysql> system sudo ls -lh /var/lib/mysql/testtotal 112K-rw-r----- 1 mysql mysql   61 Apr 28 10:18 db.opt-rw-r----- 1 mysql mysql 8.4K May  7 17:03 t.frm-rw-r----- 1 mysql mysql  96K May  7 17:03 t.ibd</pre>

[](http://zhongmingmao.me/2017/0... "General Tablespace")General Tablespace

  1. General TablespaceMySQL 5.7.6引入的新特性,具體內容請參照下面連接
    15.7.9 InnoDB General Tablespaces

[](http://zhongmingmao.me/2017/0... "Segment")Segment

segment

  1. Segment分爲三種

    1. Leaf node segment數據段,B+Tree的葉子節點
    2. Non-Leaf node segment索引段,B+Tree的非葉子節點
    3. Rollback segment:回滾段,存放undo log,默認是位於System Tablespace
  2. InnoDB中的B+Tree索引,由Leaf node segmentNon-Leaf node segment組成
  3. 一個Segment由多個Extent和Page組成

[](http://zhongmingmao.me/2017/0... "Extent")Extent

  1. Extent是由連續頁(默認頁大小爲16KB)組成,在默認頁大小時,爲64個連續頁,大小爲64*16KB=1MB

    • 不一樣頁大小:4KB*256 or 8KB*128 or 16KB*64 or 32KB*64 or 64KB*64
  2. 爲了保證頁的連續性,InnoDB能夠一次性從磁盤申請4個Extent
  3. 爲了節省磁盤空間,如表的數據量很小(Leaf node segmentNon-Leaf node segment都很小)或Rollback segment,Segment一開始不會直接申請Extent,而是先用32個碎片頁(用於葉子節點)來存放數據,用完以後才繼續對Extent(1MB)的申請

Page

  1. Page是InnoDB磁盤管理的最小單位,變量爲innodb_page_size
<pre>1234567</pre> <pre>mysql>  SHOW VARIABLES LIKE 'innodb_page_size';+------------------+-------+  Variable_name      Value  +------------------+-------+  innodb_page_size   16384  +------------------+-------+1 row in set (0.17 sec)</pre>

[](http://zhongmingmao.me/2017/0... "Row")Row

  1. InnoDB存儲引擎的數據是按行進行存放的
  2. 行記錄格式Row_FORMAT將在後續詳細介紹

接下來是Page數據頁詳解,這是最重要的一部分。

本文主要介紹InnoDB存儲引擎的數據頁結構

[](http://zhongmingmao.me/2017/0... "數據頁結構")數據頁結構

page-structure

[](http://zhongmingmao.me/2017/0... "File Header")File Header

參考連接:Fil Header

  1. 總共38 Bytes,記錄頁的頭信息
名稱 大小(Bytes) 描述
FIL_PAGE_SPACE 4 該頁的checksum
FIL_PAGE_OFFSET 4 該頁在表空間中的頁偏移量
FIL_PAGE_PREV 4 該頁的上一個頁
FIL_PAGE_NEXT 4 該頁的下一個頁
FIL_PAGE_LSN 8 該頁最後被修改的LSN
FIL_PAGE_TYPE 2 該頁的類型,0x45BF爲數據頁
FIL_PAGE_FILE_FLUSH_LSN 8 獨立表空間中爲0
FIL_PAGE_ARCH_LOG_NO 4 該頁屬於哪個表空間

[](http://zhongmingmao.me/2017/0... "Page Header")Page Header

參考連接:Page Header

  1. 總共56 Bytes,記錄頁的狀態信息
名稱 大小(Bytes) 描述
PAGE_N_DIR_SLOTS 2 Page DirectorySlot的數量,初始值爲2
PAGE_HEAP_TOP 2 堆中第一個記錄的指針
PAGE_N_HEAP 2 堆中的記錄數,初始值爲2
PAGE_FREE 2 指向可重用空間的首指針
PAGE_GARBAGE 2 已標記爲刪除(deleted_flag)的記錄的字節數
PAGE_LAST_INSERT 2 最後插入記錄的位置
PAGE_DIRECTION 2 最後插入的方向,PAGE_LEFT(0x01)PAGE_RIGHT(0x02)PAGE_NO_DIRECTION(0x05)
PAGE_N_DIRECTION 2 一個方向上連續插入記錄的數量
PAGE_N_RECS 2 該頁中記錄(User Record)的數量
PAGE_MAX_TRX_ID 8 修改該頁的最大事務ID(僅在輔助索引中定義)
PAGE_LEVEL 2 該頁在索引樹中位置,0000表明葉子節點
PAGE_INDEX_ID 8 索引ID,表示該頁屬於哪一個索引
PAGE_BTR_SEG_LEAF 10 B+Tree葉子節點所在Leaf Node Segment的Segment Header(可有可無)
PAGE_BTR_SEG_TOP 10 B+Tree非葉子節點所在Non-Leaf Node Segment的Segment Header(可有可無)

[](http://zhongmingmao.me/2017/0... "Infimum + Supremum Records")Infimum + Supremum Records

參考連接:The Infimum and Supremum Records

  1. 每一個數據頁中都有兩個虛擬的行記錄,用來限定記錄(User Record)的邊界(Infimum爲下界Supremum爲上界
  2. InfimumSupremum頁被建立是自動建立,不會被刪除
  3. CompactRedundant行記錄格式下,InfimumSupremum佔用的字節數是不同

infimum-supremum

[](http://zhongmingmao.me/2017/0... "User Records")User Records

參考連接:User Records

  1. 存儲實際插入的行記錄
  2. Page HeaderPAGE_HEAP_TOPPAGE_N_HEAPHEAP,實際上指的是Unordered User Record List

    • InnoDB不想每次都依據B+Tree鍵的順序插入新行,由於這可能須要移動大量的數據
    • 所以InnoDB插入新行時,一般是插入到當前行的後面(Free Space的頂部)或者是已刪除行留下來的空間
  3. 爲了保證訪問B+Tree記錄的順序性,在每一個記錄中都有一個指向下一條記錄的指針,以此構成了一條單向有序鏈表

[](http://zhongmingmao.me/2017/0... "Free Space")Free Space

  1. 空閒空間,數據結構是鏈表,在一個記錄被刪除後,該空間會被加入到空閒鏈表中

[](http://zhongmingmao.me/2017/0... "Page Directory")Page Directory

參考連接:Page Directory

  1. 存放着行記錄User Record)的相對位置(不是偏移量)
  2. 這裏的行記錄指針稱SlotDirectory Slot,每一個Slot佔用2Byte
  3. 並非每個行記錄都有一個Slot,一個Slot中可能包含多條行記錄,經過行記錄中n_owned字段標識
  4. Infimum的n_owned老是1Supremum的n_owned爲[1,8]User Record的n_owned爲[4,8]
  5. Slot是按照索引鍵值的順序進行逆序存放(Infimum是下界,Supremum是上界),能夠利用二分查找快速地定位一個粗略的結果,而後再經過next_record進行精確查找
  6. B+Tree索引自己並不能直接找到具體的一行記錄,只能找到該行記錄所在的頁

    • 數據庫把頁載入到內存中,而後經過Page Directory再進行二分查找
    • 二分查找時間複雜度很低,又在內存中進行查找,這部分的時間基本開銷能夠忽略

[](http://zhongmingmao.me/2017/0... "File Trailer")File Trailer

參考連接:Fil Trailer

  1. 總共8 Bytes,爲了檢測頁是否已經完整地寫入磁盤
  2. 變量innodb_checksums,InnoDB從磁盤讀取一個頁時是否會檢測頁的完整性
  3. 變量innodb_checksum_algorithm檢驗和算法

微信公衆號【黃小斜】做者是螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,堅持學習和寫做,相信終身學習的力量!關注公衆號後回覆」架構師「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源

相關文章
相關標籤/搜索