表資料都刪了一半,可表文件還是那麼大?
摘要:由於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