爲何表數據刪除一半,表文件大小不變?

 

一、概念

有些時候數據庫佔用的空間比較大,因此把表數據刪除不少,可是數據庫表佔用大小沒有改變。數據庫

 

本章說一下,數據表空間回收。安全

 

一個InnoDB表包含兩部分,即:表結構定義和數據。在MySQL 8.0版本之前,表結構是存在以.frm爲後綴的文件裏。而MySQL 8.0版本,則已經容許把表結構定義放在系統數據表中了。由於表結構定義佔用的空間很小,因此主要討論的是表數據。優化

 

二、參數innodb_file_per_table

表數據既能夠存在共享表空間裏,也能夠是單獨的文件。這個行爲是由參數innodb_file_per_table控制的:線程

 

1)這個參數設置爲OFF表示的是,表的數據放在系統共享表空間,也就是跟數據字典放在一塊兒;日誌

 

2)這個參數設置爲ON表示的是,每一個InnoDB表數據存儲在一個以 .ibd爲後綴的文件中。server

 

從MySQL 5.6.6版本開始,它的默認值就是ON了。blog

 

建議不論使用MySQL的哪一個版本,都將這個值設置爲ON。由於,一個表單獨存儲爲一個文件更容易管理,並且在你不須要這個表的時候,經過drop table命令,系統就會直接刪除這個文件。而若是是放在共享表空間中,即便表刪掉了,空間也是不會回收的。索引

 

因此,將innodb_file_per_table設置爲ON,是推薦作法。資源

 

咱們在刪除整個表的時候,可使用drop table命令回收表空間。可是,咱們遇到的更多的刪除數據的場景是刪除某些行,這時就遇到問題是:表中的數據被刪除了,可是表空間卻沒有被回收。it

 

三、數據刪除流程

InnoDB裏的數據都是用B+樹的結構組織的。

 

假設,咱們要刪掉R4這個記錄,InnoDB引擎只會把R4這個記錄標記爲刪除。若是以後要再插入一個ID在300和600之間的記錄時,可能會複用這個位置。可是,磁盤文件的大小並不會縮小。

 

如今,你已經知道了InnoDB的數據是按頁存儲的,那麼若是咱們刪掉了一個數據頁上的全部記錄,會怎麼樣?

 

答案是,整個數據頁就能夠被複用了。

 

數據頁的複用和記錄的複用是不一樣的。

 

記錄的複用,只限於符合範圍條件的數據。好比上面的這個例子,R4這條記錄被刪除後,若是插入一個ID是400的行,能夠直接複用這個空間。但若是插入的是一個ID是800的行,就不能複用這個位置了。

 

而當整個頁從B+樹裏面摘掉之後,能夠複用到任何位置。以圖1爲例,若是將數據頁page A上的全部記錄刪除之後,page A會被標記爲可複用。這時候若是要插入一條ID=50的記錄須要使用新頁的時候,page A是能夠被複用的。

 

若是相鄰的兩個數據頁利用率都很小,系統就會把這兩個頁上的數據合到其中一個頁上,另一個數據頁就被標記爲可複用。

 

進一步地,若是咱們用delete命令把整個表的數據刪除呢?結果就是,全部的數據頁都會被標記爲可複用。可是磁盤上,文件不會變小。

 

你如今知道了,delete命令其實只是把記錄的位置,或者數據頁標記爲了「可複用」,但磁盤文件的大小是不會變的。也就是說,經過delete命令是不能回收表空間的。這些能夠複用,而沒有被使用的空間,看起來就像是「空洞」。

 

實際上,不止是刪除數據會形成空洞,插入數據也會。

 

若是數據是按照索引遞增順序插入的,那麼索引是緊湊的。但若是數據是隨機插入的,就可能形成索引的數據頁分裂。

 

假設圖1中page A已經滿了,這時我要再插入一行數據,會怎樣呢?

 

能夠看到,因爲page A滿了,再插入一個ID是550的數據時,就不得再也不申請一個新的頁面page B來保存數據了。頁分裂完成後,page A的末尾就留下了空洞(注意:實際上,可能不止1個記錄的位置是空洞)。

 

另外,更新索引上的值,能夠理解爲刪除一箇舊的值,再插入一個新值。不難理解,這也是會形成空洞的。

 

也就是說,通過大量增刪改的表,都是多是存在空洞的。因此,若是可以把這些空洞去掉,就能達到收縮表空間的目的。

 

而重建表,就能夠達到這樣的目的。

 

一、重建表

若是你如今有一個表A,須要作空間收縮,爲了把表中存在的空洞去掉,你能夠怎麼作呢?

 

你能夠新建一個與表A結構相同的表B,而後按照主鍵ID遞增的順序,把數據一行一行地從表A裏讀出來再插入到表B中。

 

因爲表B是新建的表,因此表A主鍵索引上的空洞,在表B中就都不存在了。顯然地,表B的主鍵索引更緊湊,數據頁的利用率也更高。若是咱們把表B做爲臨時表,數據從表A導入表B的操做完成後,用表B替換A,從效果上看,就起到了收縮表A空間的做用。

 

這裏,你可使用alter table A engine=InnoDB命令來重建表。在MySQL 5.5版本以前,這個命令的執行流程跟咱們前面描述的差很少,區別只是這個臨時表B不須要你本身建立,MySQL會自動完成轉存數據、交換表名、刪除舊錶的操做。

 

圖3 改鎖表DDL

顯然,花時間最多的步驟是往臨時表插入數據的過程,若是在這個過程當中,有新的數據要寫入到表A的話,就會形成數據丟失。所以,在整個DDL過程當中,表A中不能有更新。也就是說,這個DDL不是Online的。

 

而在MySQL 5.6版本開始引入的Online DDL,對這個操做流程作了優化。

 

簡單描述一下引入了Online DDL以後,重建表的流程:

 

1)創建一個臨時文件,掃描表A主鍵的全部數據頁;

 

2)用數據頁中表A的記錄生成B+樹,存儲到臨時文件中;

 

3)生成臨時文件的過程當中,將全部對A的操做記錄在一個日誌文件(row log)中,對應的是圖中state2的狀態;

 

4)臨時文件生成後,將日誌文件中的操做應用到臨時文件,獲得一個邏輯數據上與表A相同的數據文件,對應的就是圖中state3的狀態;

 

5)用臨時文件替換表A的數據文件。

 

圖4 Online DDL

能夠看到,與圖3過程的不一樣之處在於,因爲日誌文件記錄和重放操做這個功能的存在,這個方案在重建表的過程當中,容許對錶A作增刪改操做。這也就是Online DDL名字的來源。

 

圖4的流程中,alter語句在啓動的時候須要獲取MDL寫鎖,可是這個寫鎖在真正拷貝數據以前就退化成讀鎖了。

 

爲何要退化呢?爲了實現Online,MDL讀鎖不會阻塞增刪改操做。

 

那爲何不乾脆直接解鎖呢?爲了保護本身,禁止其餘線程對這個表同時作DDL。

 

而對於一個大表來講,Online DDL最耗時的過程就是拷貝數據到臨時表的過程,這個步驟的執行期間能夠接受增刪改操做。因此,相對於整個DDL過程來講,鎖的時間很是短。對業務來講,就能夠認爲是Online的。

 

須要補充說明的是,上述的這些重建方法都會掃描原表數據和構建臨時文件。對於很大的表來講,這個操做是很消耗IO和CPU資源的。所以,若是是線上服務,你要很當心地控制操做時間。若是想要比較安全的操做的話,我推薦你使用GitHub開源的gh-ost來作。

 

五、online和inplace

一個跟DDL有關的、容易混淆的概念inplace的區別。

 

在圖3中,咱們把表A中的數據導出來的存放位置叫做tmp_table。這是一個臨時表,是在server層建立的。

 

在圖4中,根據表A重建出來的數據是放在「tmp_file」裏的,這個臨時文件是InnoDB在內部建立出來的。整個DDL過程都在InnoDB內部完成。對於server層來講,沒有把數據挪動到臨時表,是一個「原地」操做,這就是「inplace」名稱的來源。

 

若是你有一個1TB的表,如今磁盤間是1.2TB,能不能作一個inplace的DDL呢?

 

答案是不能。由於,tmp_file也是要佔用臨時空間的。

 

咱們重建表的這個語句alter table t engine=InnoDB,其實隱含的意思是:

 

alter table t engine=innodb,ALGORITHM=inplace;

跟inplace對應的就是拷貝表的方式了,用法是:

 

alter table t engine=innodb,ALGORITHM=copy;

當你使用ALGORITHM=copy的時候,表示的是強制拷貝表,對應的流程就是圖3的操做過程。

 

但我這樣說你可能會以爲,inplace跟Online是否是就是一個意思?

 

其實不是的,只是在重建表這個邏輯中恰好是這樣而已。

 

好比,若是我要給InnoDB表的一個字段加全文索引,寫法是:

 

alter table t add FULLTEXT(field_name);

這個過程是inplace的,但會阻塞增刪改操做,是非Online的。

 

若是說這兩個邏輯之間的關係是什麼的話,能夠歸納爲:

 

DDL過程若是是Online的,就必定是inplace的;

 

反過來未必,也就是說inplace的DDL,有可能不是Online的。截止到MySQL 8.0,添加全文索引(FULLTEXT index)和空間索引(SPATIAL index)就屬於這種狀況。

 

最後,咱們再延伸一下。

 

在第10篇文章《MySQL爲何有時候會選錯索引》的評論區中,有同窗問到使用optimize table、analyze table和alter table這三種方式重建表的區別。這裏,我順便再簡單和你解釋一下。

 

從MySQL 5.6版本開始,alter table t engine = InnoDB(也就是recreate)默認的就是上面圖4的流程了;

analyze table t 其實不是重建表,只是對錶的索引信息作從新統計,沒有修改數據,這個過程當中加了MDL讀鎖;

optimize table t 等於recreate+analyze。

 

六、小結

這章討論了數據庫中收縮表空間的方法。

 

若是要收縮一個表,只是delete掉表裏面不用的數據的話,表文件的大小是不會變的,你還要經過alter table命令重建表,才能達到表文件變小的目的。介紹了重建表的兩種實現方式,Online DDL的方式是能夠考慮在業務低峯期使用的,而MySQL 5.5及以前的版本,這個命令是會阻塞DML的,這個你須要特別當心。

相關文章
相關標籤/搜索