轉自:https://draveness.me/mysql-in...html
做爲一名開發人員,在平常的工做中會難以免地接觸到數據庫,不管是基於文件的 sqlite 仍是工程上使用很是普遍的 MySQL、PostgreSQL,可是一直以來也沒有對數據庫有一個很是清晰而且成體系的認知,因此最近兩個月的時間看了幾本數據庫相關的書籍而且閱讀了 MySQL 的官方文檔,但願對各位瞭解數據庫的、不瞭解數據庫的有所幫助。node
本文中對於數據庫的介紹以及研究都是在 MySQL 上進行的,若是涉及到了其餘數據庫的內容或者實現會在文中單獨指出。mysql
不少開發者在最開始時其實都對數據庫有一個比較模糊的認識,以爲數據庫就是一堆數據的集合,可是實際卻比這複雜的多,數據庫領域中有兩個詞很是容易混淆,也就是_數據庫_和_實例_:程序員
對於數據庫和實例的定義都來自於 MySQL 技術內幕:InnoDB 存儲引擎 一書,想要了解 InnoDB 存儲引擎的讀者能夠閱讀這本書籍。
在 MySQL 中,實例和數據庫每每都是一一對應的,而咱們也沒法直接操做數據庫,而是要經過數據庫實例來操做數據庫文件,能夠理解爲數據庫實例是數據庫爲上層提供的一個專門用於操做的接口。面試
在 Unix 上,啓動一個 MySQL 實例每每會產生兩個進程,mysqld
就是真正的數據庫服務守護進程,而 mysqld_safe
是一個用於檢查和設置 mysqld
啓動的控制程序,它負責監控 MySQL 進程的執行,當 mysqld
發生錯誤時,mysqld_safe
會對其狀態進行檢查並在合適的條件下重啓。算法
MySQL 從第一個版本發佈到如今已經有了 20 多年的歷史,在這麼多年的發展和演變中,整個應用的體系結構變得愈來愈複雜:sql
最上層用於鏈接、線程處理的部分並非 MySQL 『發明』的,不少服務都有相似的組成部分;第二層中包含了大多數 MySQL 的核心服務,包括了對 SQL 的解析、分析、優化和緩存等功能,存儲過程、觸發器和視圖都是在這裏實現的;而第三層就是 MySQL 中真正負責數據的存儲和提取的存儲引擎,例如:InnoDB、MyISAM 等,文中對存儲引擎的介紹都是對 InnoDB 實現的分析。數據庫
在整個數據庫體系結構中,咱們可使用不一樣的存儲引擎來存儲數據,而絕大多數存儲引擎都以二進制的形式存儲數據;這一節會介紹 InnoDB 中對數據是如何存儲的。後端
在 InnoDB 存儲引擎中,全部的數據都被邏輯地存放在表空間中,表空間(tablespace)是存儲引擎中最高的存儲邏輯單位,在表空間的下面又包括段(segment)、區(extent)、頁(page):緩存
同一個數據庫實例的全部表空間都有相同的頁大小;默認狀況下,表空間中的頁大小都爲 16KB,固然也能夠經過改變 innodb_page_size
選項對默認大小進行修改,須要注意的是不一樣的頁大小最終也會致使區大小的不一樣:
從圖中能夠看出,在 InnoDB 存儲引擎中,一個區的大小最小爲 1MB,頁的數量最少爲 64 個。
MySQL 使用 InnoDB 存儲表時,會將表的定義和數據索引等信息分開存儲,其中前者存儲在 .frm
文件中,後者存儲在 .ibd
文件中,這一節就會對這兩種不一樣的文件分別進行介紹。
不管在 MySQL 中選擇了哪一個存儲引擎,全部的 MySQL 表都會在硬盤上建立一個 .frm
文件用來描述表的格式或者說定義;.frm
文件的格式在不一樣的平臺上都是相同的。
<pre>CREATE TABLE test_frm( column1 CHAR(5), column2 INTEGER);
</pre>
當咱們使用上面的代碼建立表時,會在磁盤上的 datadir
文件夾中生成一個 test_frm.frm
的文件,這個文件中就包含了表結構相關的信息:
MySQL 官方文檔中的
11.1 MySQL .frm File Format 一文對於
.frm
文件格式中的二進制的內容有着很是詳細的表述,在這裏就不展開介紹了。
InnoDB 中用於存儲數據的文件總共有兩個部分,一是系統表空間文件,包括 ibdata1
、ibdata2
等文件,其中存儲了 InnoDB 系統信息和用戶數據庫表數據和索引,是全部表公用的。
當打開 innodb_file_per_table
選項時,.ibd
文件就是每個表獨有的表空間,文件存儲了當前表的數據和相關的索引數據。
與現有的大多數存儲引擎同樣,InnoDB 使用頁做爲磁盤管理的最小單位;數據在 InnoDB 存儲引擎中都是按行存儲的,每一個 16KB 大小的頁中能夠存放 2-200 行的記錄。
當 InnoDB 存儲數據時,它可使用不一樣的行格式進行存儲;MySQL 5.7 版本支持如下格式的行存儲方式:
Antelope 是 InnoDB 最開始支持的文件格式,它包含兩種行格式 Compact 和 Redundant,它最開始並無名字;Antelope 的名字是在新的文件格式 Barracuda 出現後才起的,Barracuda 的出現引入了兩種新的行格式 Compressed 和 Dynamic;InnoDB 對於文件格式都會向前兼容,而官方文檔中也對以後會出現的新文件格式預先定義好了名字:Cheetah、Dragon、Elk 等等。
兩種行記錄格式 Compact 和 Redundant 在磁盤上按照如下方式存儲:
Compact 和 Redundant 格式最大的不一樣就是記錄格式的第一個部分;在 Compact 中,行記錄的第一部分倒序存放了一行數據中列的長度(Length),而 Redundant 中存的是每一列的偏移量(Offset),從整體上上看,Compact 行記錄格式相比 Redundant 格式可以減小 20% 的存儲空間。
當 InnoDB 使用 Compact 或者 Redundant 格式存儲極長的 VARCHAR 或者 BLOB 這類大對象時,咱們並不會直接將全部的內容都存放在數據頁節點中,而是將行數據中的前 768 個字節存儲在數據頁中,後面會經過偏移量指向溢出頁。
可是當咱們使用新的行記錄格式 Compressed 或者 Dynamic 時都只會在行記錄中保存 20 個字節的指針,實際的數據都會存放在溢出頁面中。
固然在實際存儲中,可能會對不一樣長度的 TEXT 和 BLOB 列進行優化,不過這就不是本文關注的重點了。
想要了解更多與 InnoDB 存儲引擎中記錄的數據格式的相關信息,能夠閱讀 InnoDB Record Structure
頁是 InnoDB 存儲引擎管理數據的最小磁盤單位,而 B-Tree 節點就是實際存放表中數據的頁面,咱們在這裏將要介紹頁是如何組織和存儲記錄的;首先,一個 InnoDB 頁有如下七個部分:
每個頁中包含了兩對 header/trailer:內部的 Page Header/Page Directory 關心的是頁的狀態信息,而 Fil Header/Fil Trailer 關心的是記錄頁的頭信息。
在頁的頭部和尾部之間就是用戶記錄和空閒空間了,每個數據頁中都包含 Infimum 和 Supremum 這兩個虛擬的記錄(能夠理解爲佔位符),Infimum 記錄是比該頁中任何主鍵值都要小的值,Supremum 是該頁中的最大值:
User Records 就是整個頁面中真正用於存放行記錄的部分,而 Free Space 就是空餘空間了,它是一個鏈表的數據結構,爲了保證插入和刪除的效率,整個頁面並不會按照主鍵順序對全部記錄進行排序,它會自動從左側向右尋找空白節點進行插入,行記錄在物理存儲上並非按照順序的,它們之間的順序是由 next_record
這一指針控制的。
B+ 樹在查找對應的記錄時,並不會直接從樹中找出對應的行記錄,它只能獲取記錄所在的頁,將整個頁加載到內存中,再經過 Page Directory 中存儲的稀疏索引和 n_owned
、next_record
屬性取出對應的記錄,不過由於這一操做是在內存中進行的,因此一般會忽略這部分查找的耗時。
InnoDB 存儲引擎中對數據的存儲是一個很是複雜的話題,這一節中也只是對錶、行記錄以及頁面的存儲進行必定的分析和介紹,雖然做者相信這部分知識對於大部分開發者已經足夠了,可是想要真正消化這部份內容還須要不少的努力和實踐。下文是詳細分析。
本文主要介紹InnoDB
存儲引擎的邏輯存儲結構
最高層
,全部數據
都存放在Tablespace中分類
System Tablespace
Separate Tablespace
General Tablespace
System Tablespace
即咱們常見的共享表空間
,變量爲innodb_data_file_path
,通常爲ibdata1
文件undo logs
,change buffer
,doublewrite buffer
等信息(後續將詳細介紹),在沒有開啓file-per-table
的狀況下,還會包含全部表的索引和數據
信息沒有開啓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> |
---|
Separate Tablespace
這個術語,這裏只爲了行文方便,表示在開啓file-per-table
的狀況下,每一個表有本身獨立的表空間
,變量爲innodb_file_per_table
每一個表的索引和數據信息
,後綴通常爲.ibd
96KB
好處
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> |
---|
General Tablespace
是MySQL 5.7.6
引入的新特性,具體內容請參照下面連接Segment分爲三種
Leaf node segment
:數據段
,B+Tree的葉子節點Non-Leaf node segment
:索引段
,B+Tree的非葉子節點Rollback segment
:回滾段,存放undo log
,默認是位於System Tablespace
B+Tree索引
,由Leaf node segment
和Non-Leaf node segment
組成多個Extent和Page
組成Extent
是由連續頁(默認頁大小爲16KB
)組成,在默認頁大小
時,爲64個連續頁
,大小爲64*16KB=1MB
4KB*256
or 8KB*128
or 16KB*64
or 32KB*64
or 64KB*64
頁的連續性
,InnoDB能夠一次性從磁盤申請4個Extent
節省磁盤空間
,如表的數據量很小(Leaf node segment
和Non-Leaf node segment
都很小)或Rollback segment
,Segment一開始不會直接申請Extent
,而是先用32個碎片頁
(用於葉子節點
)來存放數據,用完以後才繼續對Extent(1MB)
的申請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> |
---|
按行
進行存放的Row_FORMAT
將在後續詳細介紹接下來是Page數據頁詳解,這是最重要的一部分。
本文主要介紹InnoDB
存儲引擎的數據頁結構
參考連接:Fil Header
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 | 該頁屬於哪個表空間 |
參考連接:Page Header
56 Bytes
,記錄頁的狀態信息
名稱 | 大小(Bytes) | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS | 2 | 在Page Directory 中Slot 的數量,初始值爲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(可有可無) |
參考連接:The Infimum and Supremum Records
虛擬的行記錄
,用來限定記錄(User Record
)的邊界(Infimum爲下界
,Supremum爲上界
)Infimum
和Supremum
在頁被建立
是自動建立,不會被刪除
Compact
和Redundant
行記錄格式下,Infimum
和Supremum
佔用的字節數是不同
的參考連接:User Records
實際插入的行記錄
在Page Header
中PAGE_HEAP_TOP
、PAGE_N_HEAP
的HEAP
,實際上指的是Unordered User Record List
依據B+Tree鍵的順序
來插入新行
,由於這可能須要移動大量的數據
Free Space的頂部
)或者是已刪除行留下來的空間
順序性
,在每一個記錄中都有一個指向下一條記錄的指針
,以此構成了一條單向有序鏈表
鏈表
,在一個記錄被刪除
後,該空間會被加入到空閒鏈表中參考連接:Page Directory
行記錄
(User Record
)的相對位置
(不是偏移量)行記錄指針稱
爲Slot
或Directory Slot
,每一個Slot
佔用2Byte
並非每個行記錄都有一個Slot
,一個Slot中可能包含多條行記錄,經過行記錄中n_owned
字段標識Infimum
的n_owned老是1
,Supremum
的n_owned爲[1,8]
,User Record
的n_owned爲[4,8]
Slot
是按照索引鍵值的順序
進行逆序
存放(Infimum是下界,Supremum是上界
),能夠利用二分查找
快速地定位一個粗略的結果
,而後再經過next_record
進行精確查找
B+Tree索引
自己並不能直接找到具體的一行記錄
,只能找到該行記錄所在的頁
內存
中,而後經過Page Directory
再進行二分查找
參考連接:Fil Trailer
8 Bytes
,爲了檢測頁是否已經完整地寫入磁盤
innodb_checksums
,InnoDB從磁盤讀取一個頁
時是否會檢測頁的完整性
innodb_checksum_algorithm
,檢驗和算法
微信公衆號【黃小斜】做者是螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,堅持學習和寫做,相信終身學習的力量!關注公衆號後回覆」架構師「便可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送做者原創的Java學習指南、Java程序員面試指南等乾貨資源