一:概念
- 這里,我們還是針對 MySQL 中應用最廣泛的 InnoDB 引擎展開討論。
- 一個 InnoDB 表包含兩部分,即:表結構定義和數據。
- 在 MySQL 8.0 版本以前,表結構是存在以.frm 為后綴的文件里。
- 而 MySQL 8.0 版本,則已經允許把表結構定義放在系統數據表中了。
- 因為表結構定義占用的空間很小,所以我們今天主要討論的是表數據。
二:表數據既可以存在共享表空間里,也可以是單獨的文件。由參數 innodb_file_per_table 控制。
- 概念
- 這個參數設置為 OFF 表示的是,表的數據放在系統共享表空間,也就是跟數據字典放在一起。
- 這個參數設置為 ON 表示的是,每個 InnoDB 表數據存儲在一個以 .ibd 為后綴的文件中。
- 從 MySQL 5.6.6 版本開始,它的默認值就是 ON 了。
- 因為,一個表單獨存儲為一個文件更容易管理,而且在你不需要這個表的時候,通過 drop table 命令,系統就會直接刪除這個文件。
- 而如果是放在共享表空間中,即使表刪掉了,空間也是不會回收的。
三:數據刪除流程
- 數據
-
- 刪除
- 假設,我們要刪掉 300 的記錄,InnoDB 引擎只會把 300 這個記錄標記為刪除。
- 但是, 如果之后要再插入一個 在 300-500 之間的記錄時,可能會復用這個位置。(磁盤文件的大小並不會縮小)。
- 如果我們刪掉了一個數據頁上的所有記錄,整個數據頁就可以被復用了。(如果相鄰的兩個數據頁利用率都很小,會整合兩個數據頁,一個數據頁就被標記為可復用。)
- 如果我們用 delete 命令把整個表的數據刪除呢?
- 結果就是,所有的數據頁都會被標記為可復用。但是磁盤上,文件不會變小。
- 結論
- Delete 命令其實只是把記錄的位置,或者數據頁標記為了“可復用”,但磁盤文件的大小是不會變的。
- 也就是說,通過 Delete 命令是不能回收表空間的。
- 這些可以復用,而沒有被使用的空間,看起來就像是“空洞”。
- 實際上,不止是刪除數據會造成空洞,插入數據也會。
- 如果數據是按照索引遞增順序插入的,那么索引是緊湊的。但如果數據是隨機插入的,就可能造成索引的數據頁分裂。
- 假設我要寫入的 page A 已經滿了,在插入一行數據,也會導致頁分裂。
- 另外,更新索引上的值,可以理解為刪除一個舊的值,再插入一個新值。不難理解,這也是會造成空洞的。
- 也就是說,經過大量增刪改的表,都是可能是存在空洞的。
- 所以,如果能夠把這些空洞去掉,就能達到收縮表空間的目的。
四:重建表(去除空洞)
- 如果需要重建表,那么你能想出他是如何構建表的?
- 建立 臨時表B,把 A 表數據根據遞增的關系,放入 B 中,最后用 B 替換 A。完成重構。(A 的空間收縮,空洞消失)
- 而在MySQL 5.6 版本開始引入的 Online DDL,對這個重建操作流程做了優化。
- 流程
- 建立一個臨時文件,掃描表 A 主鍵的所有數據頁。
- 用數據頁中表 A 的記錄生成 B+ 樹,存儲到臨時文件中。
- 生成臨時文件的過程中,將所有對 A 的操作記錄在一個日志文件(row log)中。
- 臨時文件生成后,將日志文件中的操作應用到臨時文件,得到一個邏輯數據上與表 A 相同的數據文件。
- 用臨時文件替換表 A 的數據文件。
- 區別
- 不同之處在於,由於日志文件記錄和重放操作這個功能的存在,這個方案在重建表的過程中,允許對表 A 做增刪改操作。
- 這也就是 Online DDL 名字的來源。
五:如何重建表?
- alter table t engine = InnoDB(也就是 recreate)
- OnLine DDL
- analyze table t
- 其實不是重建表,只是對表的索引信息做重新統計,沒有修改數據,這個過程中加了 MDL 讀鎖;
- optimize table t
- 等於 recreate+analyze。