系列文章:MySQL系列專欄mysql
InnoDB 存儲引擎是MySQL的默認存儲引擎,是事務安全的MySQL存儲引擎。該存儲引擎是第一個完整ACID事務的MySQL存儲引擎,其特色是行鎖設計、支持MVCC、支持外鍵、提供一致性非鎖定讀,同時被設計用來最有效地利用以及使用內存和 CPU。所以頗有必要學習下InnoDB存儲引擎,它的不少架構設計思路均可以應用到咱們的應用系統設計中。程序員
咱們經過下面這張圖先對 InnoDB 存儲引擎的體系有一個總體的認識,裏面有不少細節後面會分幾篇文章來學習。web
好比要更新 user 表中 id=1 的這條數據,它的大體流程以下:sql
一、客戶端鏈接到MySQL服務器,將SQL更新語句發送到服務器;MySQL服務器鏈接池中會有一個鏈接和客戶端創建鏈接,而後後臺線程會從鏈接中獲取到要執行的SQL語句,併發送給SQL接口去調度執行。數據庫
二、增、刪、改 時,會將查詢緩存中 user 表相關的緩存都清空。緩存
三、SQL語句通過SQL解析器解析、優化器優化,獲得一個執行路徑,前面這些和執行查詢其實都是相似的。安全
四、接着由執行引擎去調用底層的存儲引擎接口,根據執行計劃完成SQL語句的執行。性能優化
① 首先查詢出要更新的數據,這一步會先判斷緩衝池(Buffer Pool)中是否已經存在這條數據,若是已經存在了,則直接從緩存池獲取數據返回。不然從磁盤數據文件中加載這條數據到緩衝池中,再返回數據。服務器
② 獲取到數據後,執行引擎會根據SQL更新數據,而後調用存儲引擎更新數據。這一步會對數據加排它鎖,避免併發更新問題。以後先寫 undolog 到緩衝池,undolog 主要用於事務回滾、MVCC等;同時,undolog 也會產生 redolog 日誌。markdown
③ 以後更新緩衝池中的數據,同時記錄 redolog 到 RedoLog緩衝池,redolog 主要用於保證數據的持久性,宕機恢復數據等。
④ 最後提交事務,雖然沒有手動 commit 提交事務,update 語句執行完成後也會有隱式的事務提交的。事務提交時,會先在MySQL服務器層面會寫入 binlog,binlog是數據持久性的保證。最後將redolog刷入磁盤,完成事務提交。
五、最底層的一部分就是磁盤上的數據文件、日誌文件等,能夠看到,InnoDB 設計了緩衝池來緩衝數據、undolog、redolog 等,這些內存中的數據最終都是要刷新到磁盤中才能保證數據不丟失的。至於爲何要這麼設計,咱們後面再分析。
從上面的圖中,咱們至少能夠精煉出以下的核心內容:
MySQL相關的知識太多了,這個MySQL系列要學習的內容主要就包含上面的一些核心知識點,咱們主要就從這些點去研究下MySQL的一些優秀設計思想,而後可以對MySQL進行一些性能優化,就差很少了。
這篇文章咱們先看下MySQL和InnoDB的數據文件結構,爲後面的研究打下基礎。
咱們能夠經過 datadir
這個系統變量查看MySQL的數據目錄位置,默認是在 /var/lib/mysql
下。
mysql> show variables like 'datadir';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| datadir | /var/lib/mysql/ |
+---------------+-----------------+
複製代碼
在一個全新安裝的數據庫的數據目錄下,能夠看到以下的一些初始化的文件和目錄。
咱們能夠重點關注 ibdata一、ib_logfile0、ib_logfile1
這幾個文件,之後會講到。ibdata1 是共享表空間,ib_logfile0、ib_logfile1 是 redo 日誌文件。
還有一個 f4e2d8fde38c.pid
的文件,當MySQL實例啓動時,會將本身的進程ID寫入一個pid文件。該文件可由參數pid_file
控制,默認位於數據庫目錄下,文件名爲主機名.pid
。
mysql> show variables like 'pid_file';
+---------------+---------------------------------+
| Variable_name | Value |
+---------------+---------------------------------+
| pid_file | /var/lib/mysql/f4e2d8fde38c.pid |
+---------------+---------------------------------+
複製代碼
MySQL默認建立了四個系統數據庫,除了 information_schema
,另外三個都會有一個目錄與之對應。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
複製代碼
咱們經過 create database xx;
建立一個測試數據庫,並指定了字符集爲 utf8mb4
:
mysql> create database test default character set utf8mb4;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
複製代碼
建立數據庫後就會看到多了一個同名的目錄,也就是說MySQL中的數據庫在文件系統中其實就是數據目錄下的一個子目錄。
進入數據庫目錄下能夠看到,建立數據庫時會同步建立一個名爲 db.opt
的文件,這個文件中包含了該數據庫的各類屬性,好比說該數據庫默認的字符集和比較規則等。
前邊提到了MySQL的幾個系統數據庫,下面簡單看下每一個數據庫都是幹什麼的。
mysql
這個數據庫的核心,它存儲了MySQL的用戶帳戶和權限信息,一些存儲過程、事件的定義信息,一些運行過程當中產生的日誌信息,一些幫助信息以及時區信息等。
information_schema
這個數據庫保存着MySQL服務器全部其餘數據庫的信息,好比表、視圖、觸發器、列、索引、鎖、事務等等。這些信息並非真實的用戶數據,而是一些描述性信息,也稱之爲元數據。
performance_schema
這個數據庫主要保存MySQL服務器運行過程當中的一些狀態信息,包括統計最近執行了哪些語句,在執行過程的每一個階段都花費了多長時間,內存的使用狀況等等信息。
sys
這個數據庫主要是經過視圖的形式把 information_schema 和 performance_schema 結合起來,讓程序員能夠更方便的瞭解MySQL服務器的一些性能信息。
在 test 數據庫下,先用下面的SQL建立一張 user
表,指定的存儲引擎爲 InnoDB:
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(60) NOT NULL COMMENT '用戶名',
`nickname` varchar(240) DEFAULT NULL COMMENT '暱稱',
`age` int(11) DEFAULT NULL COMMENT '年齡',
PRIMARY KEY (`id`),
UNIQUE KEY `user_uk_username` (`username`) USING BTREE
) ENGINE=InnoDB;
複製代碼
建立完成以後就能夠看到這張表了:
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| user |
+----------------+
複製代碼
這個時候再看 test 目錄,會發現多了兩個文件:
user.frm
:表結構定義文件,格式爲 表名.frm
user.ibd
:表空間文件,格式爲 表名.ibd
不論表採用哪一種存儲引擎,每張表都會有一個以.frm
爲後綴名的文件,這個文件記錄了該表的表結構定義。這個.frm
文件是以二進制格式存儲的,直接打開會亂碼。
InnoDB將數據按表空間(tablespace)進行存儲,MySQL數據目錄下名爲ibdata1
的文件就是默認的表空間文件,也稱爲共享表空間。能夠經過參數innodb_data_file_path
對其進行設置,格式以下:
innodb_data_file_path=datafile1[; datafile2]...
複製代碼
能夠經過多個文件組成一個表空間,同時制定文件的屬性,如:
innodb_data_file_path=/db/ibdata1:2000M;/dr2/db/ibdata2:2000M:autoextend
複製代碼
這裏將 /db/ibdata1 和 /dr2/db/ibdata2 兩個文件用來組成表空間,同時,兩個文件的文件名後都跟了屬性,表示文件 idbdata1 的大小爲2000MB,文件ibdata2的大小爲2000MB,若是用完了這2000MB,該文件能夠自動增加(autoextend
)。
能夠看到默認的 ibdata1 的大小爲12M,且支持自動擴展。
mysql> show variables like 'innodb_data_file_path';
+-----------------------+------------------------+
| Variable_name | Value |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+
複製代碼
若設置了參數innodb_file_per_table
,InnoDB每一個表將產生一個獨立表空間。獨立表空間的命名規則爲 表名.ibd
,例如前面的 user.ibd
。這個配置默認是開啓的,就是每張表都有一個獨立的表空間文件來存儲數據。
mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
複製代碼
InnoDB將全部數據都存放在表空間中,表空間又由段(segment)、區(extent)、頁(page)組成。InnoDB存儲引擎的邏輯存儲結構大體以下圖。下面咱們就一個個來看看。
表空間能夠看作是InnoDB存儲引擎邏輯結構的最高層,全部的數據都存放在表空間中。在默認狀況下InnoDB存儲引擎有一個共享表空間ibdata1
,全部數據都存放在這個表空間內。
若是啓用了參數innodb_file_per_table
,則每張表內的數據能夠單獨放到一個表空間內。須要注意的是,這些單獨的表空間文件僅存儲該表的數據、索引和插入緩衝Bitmap等信息,其他信息仍是存放在共享表空間中,例如 undo日誌、插入緩衝索引頁、系統事務信息、二次寫緩衝等。
所以即便在啓用了參數innodb_file_per_table
以後,共享表空間的大小仍是會不斷地增長,例如事務中寫入了undo日誌,就算回滾了,共享表空間的大小也不會縮小。可是會判斷這些undo信息是否還須要,不須要的話,就會將這些空間標記爲可用空間,供下次重複使用。
InnoDB的數據是按行進行存放的,每一個頁存放的行記錄最多容許存放16KB / 2 -200
行的記錄,即7992
行記錄。
每行記錄根據不一樣的行格式、不一樣的數據類型,會有不一樣的存儲方式。每行除了記錄咱們保存的數據以外,還可能會記錄事務ID(DB_TRX_ID),回滾指針(DB_ROLL_PTR)等。
頁(Page)
是 InnoDB 磁盤管理的最小單位,默認每一個頁的大小爲16KB
,也就是最多能保證16KB的連續存儲空間。
InnoDB 將數據劃分爲若干個頁,以頁做爲磁盤和內存之間交互的基本單位,也就是一次最少從磁盤中讀取一頁16KB的內容到內存中,一次最少把內存中的16KB內容刷新到磁盤中。
InnoDB 爲了避免同的目的設計了若干種不一樣類型的頁面,經常使用的頁面類型有:
在介紹後面的內容前,這一小節先簡單介紹下表中的數據是如何組織的,後面會單獨一篇文章來說索引。
在InnoDB中,表都是根據主鍵順序存放數據的,這種存儲方式的表稱爲索引組織表
。在InnoDB表中,每張表都有個主鍵,若是在建立表時沒有顯式地定義主鍵,則InnoDB會按以下方式選擇或建立主鍵:
row_id
的6字節的隱藏列做爲主鍵。爲了能快速的從磁盤中檢索出數據,InnoDB採用 B+樹
結構來組織數據,經過 B+樹
組織起來的結構大概就像下圖這個樣子。B+樹是多層的,B+樹每一層中的頁都會造成一個雙向鏈表。在這棵樹中,只有最底層的葉節點才存儲數據,這些數據是按主鍵順序存儲的。上層的非葉子節點存儲的則是索引目錄,索引目錄則根據主鍵區間劃分了多個頁和層級,這樣就能夠經過相似二分法的方式快速找到某條數據所在的頁,而後經過主鍵定位到具體的某條數據。
能夠看到,InnoDB存儲引擎表是索引組織的,數據即索引,索引即數據。
在默認狀況下,InnoDB存儲引擎頁的大小爲16KB
,表空間中的頁就太多了。爲了更好的管理這些頁,InnoDB 將物理位置上連續的64個頁劃爲一個區
,任何狀況下,每一個區的大小都爲1MB
。
B+樹中每一層都是經過雙向鏈表鏈接起來的,若是是以頁爲單位來分配存儲空間,原本鏈表中相鄰的兩個頁之間的物理位置就可能離得很是遠,那麼磁盤查詢時就會有大量的隨機I/O,隨機I/O是很是慢的。因此應該儘可能讓鏈表中相鄰的頁的物理位置也相鄰,這樣能夠消除不少的隨機I/O,使用順序I/O,尤爲是在進行範圍查詢的時候。
因此在表中數據量大的時候,爲某個索引分配空間的時候就再也不按照頁爲單位分配了,而是按照區爲單位分配,甚至在表中的數據很是多的時候,能夠一次性分配多個連續的區。
不管是系統表空間仍是獨立表空間,均可以當作是由若干個區組成的,每一個區64個頁,而後每256個區又被劃分紅一組。
第一個組最開始的3個頁面的類型是固定的,也就是第一個區(extent0)最開始的三個頁。分別是:
FSP_HDR
:用來登記整個表空間的一些總體屬性以及本組全部區的屬性,整個表空間只有一個 FSP_HDR 類型的頁面。IBUF_BITMAP
:存儲本組全部區的全部頁面關於 INSERT BUFFER 的信息。INODE
:索引節點信息。其他各組則是最開始的2個頁面的類型是固定的,分別是:
XDES
:用來登記本組256個區的屬性。IBUF_BITMAP
:存儲本組全部的區的全部頁面關於 INSERT BUFFER 的信息。從這裏也能夠看出,索引數據並不時連續存儲在區中,由於其中有些頁面被用來存儲額外的一些管理信息了。
從前面B+樹的結構知道,B+樹分爲葉子節點和非葉子節點,最底層的葉子節點才存儲了數據,非葉子節點是索引目錄。若是將葉子節點頁和非葉子節點頁混合在一塊兒存儲,那在檢索數據的時候一樣也會有大量的隨機I/O。
因此 InnoDB 又提出了段的概念,常見的段有數據段、索引段、回滾段等。段是一個邏輯上的概念,並不對應表空間中某一個連續的物理區域,它由若干個完整的區組成(還會包含一些碎片頁),不一樣的段不能使用同一個區。
存放葉子節點的區的集合就是數據段
,存放非葉子節點的區的集合就是索引段
。也就是說一個索引會生成2個段,一個葉子節點段(數據段),一個非葉子節點段(索引段)。