1. 程式人生 > 其它 >表資料都刪了一半,可表文件還是那麼大?

表資料都刪了一半,可表文件還是那麼大?

摘要:由於DB佔用空間太大,我刪除了大表的一半資料,可為啥這表文件的大小沒變?資料庫表的空間回收到底是怎麼做的呢?

本文分享自華為雲社群《為什麼表資料刪掉一半,表文件大小不變?》,作者: JavaEdge。

由於DB佔用空間太大,我刪除了大表的一半資料,可為啥這表文件的大小沒變?

資料庫表的空間回收到底是怎麼做的呢?

InnoDB表包含:

  • 表結構定義(所佔空間小)
  • 表資料(重點)

MySQL版本:

  • <8.0,表結構存在於 .frm 字尾檔案裡
  • 8.0,允許將表結構定義放在系統資料表。

為何直接刪除表資料無法回收表空間?

如何正確回收空間?

1 innodb_file_per_table

表資料

  • 既能存在於共享表空間
  • 也能是單獨的檔案

該行為由引數innodb_file_per_table決定:

  • OFF,表的資料放在系統共享表空間,和資料字典放一起
  • ON,每個InnoDB表資料儲存在一個 .ibd 字尾檔案

從MySQL 5.6.6版本開始,預設值為ON。

推薦無論哪個版本,都將該值設為ON:

  • 因為一個表單獨儲存為一個檔案更容易管理,不需要時,直接drop table,系統就會刪除該檔案
  • 若放在共享表空間中,即使表刪掉了,空間也不會回收

因此後續討論都預設該設定為ON。

刪除整個表時,可用drop table回收表空間。但更常見的場景是刪除某些行,於是就會發現:表中的資料被刪除了,但表空間沒有被回收!

2 資料刪除流程

InnoDB索引示意圖

假設,我們要刪掉D4,InnoDB引擎只會把D4這個記錄標記為刪除。若之後要再插入一個ID在300、600之間的記錄時,可能會複用該位置。但磁碟檔案並不會縮小。

3 InnoDB的資料按頁儲存,若刪掉一個數據頁上的所有記錄,會咋樣?

整個資料頁就能被複用了。

但是,資料頁的複用跟記錄的複用不同:

  • 記錄的複用,只限於符合範圍條件的資料。比如D4記錄被刪除後,若插入一個ID=400的行,可直接複用該空間。但若插入ID=800,就不能複用該位置
  • 而當整個頁從B+樹裡摘掉後,可複用到任何位置

若將資料頁pageA上的所有記錄刪除後,pageA會被標記為可複用。這時若插入一條ID=20的記錄,需要使用新頁時,pageA就能被複用。

若相鄰兩個資料頁利用率都很小,系統就會把這倆頁上的資料合到其中一個頁上,另外一個數據頁就被標記為【可複用】。

4 若用delete命令刪除整個表的資料呢?

所有的資料頁都會被標記為【可複用】,但磁碟上的檔案不會變小。

delete命令其只是將記錄的位置或資料頁標記為“可複用”,但磁碟檔案的大小不會變。即通過delete命令不能回收表空間。這些可以複用但實際沒有被使用的空間,看起來就像“空洞”。

不僅刪除資料會造成空洞,插入資料也會。

5 插入資料導致的“空洞”

若資料按索引遞增順序插入,則索引是緊湊的。但若資料是隨機插入的,就可能造成索引的資料頁分裂。

假設pageA已滿,此時再插入一行資料,會怎樣呢?

插入資料導致頁分裂

由於pageA滿,再插入ID=550時,就得再申請一個新頁面pageB儲存資料。頁分裂完成後,pageA末尾就留下空洞(實際可能不止1個記錄的位置是空洞)。

6 更新索引上的值會導致空洞嗎?

更新可理解為刪除一箇舊值,再插入新值。所以也會造成空洞。

綜上,經過大量增刪改的表,都可能存在空洞。若能去掉這些空洞,就能達到收縮表空間的目的。重建表,就能達到目的。

7 重建表

若現在有一表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會自動完成轉存資料、交換表名、刪除舊錶的操作。

改鎖表DDL:

往臨時表插入資料的過程最耗時,若在此過程中,有新資料要寫入到表A,就會造成資料丟失。因此,整個DDL過程中,表A不能有更新,即這DDL不是Online的。

MySQL 5.6版本引入Online DDL,優化了該操作流程。

引入Online DDL後,重建表的流程

  • 建立一個臨時檔案,掃描表A主鍵的所有資料頁
  • 用資料頁中表A的記錄生成B+樹,儲存到臨時檔案
  • 生成臨時檔案的過程中,將所有對A的操作記錄在一個日誌檔案(row log),對應圖中state2
  • 臨時檔案生成後,將日誌檔案中的操作應用到臨時檔案,得到一個邏輯資料上與表A相同的資料檔案,對應state3

用臨時檔案替換表A的資料檔案。

Online DDL:

和上圖不同在於,由於日誌檔案記錄和重放操作的存在,該方案在重建表的過程中,允許對錶A做增刪改,這就是Online DDL名字來源。

DDL之前還要拿MDL寫鎖的,這也能叫Online DDL?

確實,圖Online DDL流程中,alter語句在啟動的時候需獲取MDL寫鎖,但該寫鎖在真正拷貝資料前就退化成讀鎖了。

為什麼要退化?

為了實現Online,MDL讀鎖不會阻塞增刪改操作。

為何不直接解鎖?

為了保護自己,禁止其他執行緒對該表同時做DDL。對一個大表,Online DDL最耗時過程就是拷貝資料到臨時表時,這個步驟的執行期間可接受增刪改操作。所以,相對於整個DDL過程,鎖的時間非常短。對業務來說,就可認為是Online的。

上述這些重建方法都會掃描原表資料、構建臨時檔案。對於大表,這很消耗IO和CPU。因此,若是線上服務,要謹慎控制操作時間。若想要較安全的操作,推薦使用GitHub開源的gh-ost。

Online 和 inplace

圖改鎖表DDL中,把表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)就屬於這種情況。

使用optimize table、analyze table和alter table這三種方式重建表

從MySQL 5.6版本開始

  • alter table t engine = InnoDB(也就是recreate)預設的就是上面圖Online DDL的流程了
  • analyze table t 其實不是重建表,只是對錶的索引資訊做重新統計,沒有修改資料,這個過程中加了MDL讀鎖
  • optimize table t =recreate+analyze

 

點選關注,第一時間瞭解華為雲新鮮技術~