常常會有同窗來問我,個人數據庫佔用空間太大,我把一個最大的表刪掉了一半的數據,怎麼表文件的大小仍是沒變?數據庫
那麼今天,我就和你聊聊數據庫表的空間回收,看看如何解決這個問題。安全
這裏,咱們仍是針對 MySQL 中應用最普遍的 InnoDB 引擎展開討論。一個 InnoDB 表包含兩部分,即:表結構定義和數據。在 MySQL 8.0 版本之前,表結構是存在以.frm 爲後
綴的文件裏。而 MySQL 8.0 版本,則已經容許把表結構定義放在系統數據表中了。由於表結構定義佔用的空間很小,因此咱們今天主要討論的是表數據。bash
接下來,我會先和你說明爲何簡單地刪除表數據達不到表空間回收的效果,而後再和你介紹正確回收空間的方法。性能
表數據既能夠存在共享表空間裏,也能夠是單獨的文件。這個行爲是由參數innodb_file_per_table 控制的:優化
1. 這個參數設置爲 OFF 表示的是,表的數據放在系統共享表空間,也就是跟數據字典放在一塊兒;spa
2. 這個參數設置爲 ON 表示的是,每一個 InnoDB 表數據存儲在一個以 .ibd 爲後綴的文件中。線程
從 MySQL 5.6.6 版本開始,它的默認值就是 ON 了。日誌
我建議你不論使用 MySQL 的哪一個版本,都將這個值設置爲 ON。由於,一個表單獨存儲爲一個文件更容易管理,並且在你不須要這個表的時候,經過 drop table 命令,系統就會
直接刪除這個文件。而若是是放在共享表空間中,即便表刪掉了,空間也是不會回收的。server
因此,將 innodb_file_per_table 設置爲 ON,是推薦作法,咱們接下來的討論都是基於這個設置展開的。blog
咱們在刪除整個表的時候,可使用 drop table 命令回收表空間。可是,咱們遇到的更多的刪除數據的場景是刪除某些行,這時就遇到了咱們文章開頭的問題:表中的數據被刪除
了,可是表空間卻沒有被回收。
咱們要完全搞明白這個問題的話,就要從數據刪除流程提及了。
咱們先再來看一下 InnoDB 中一個索引的示意圖。在前面第 4和第 5篇文章中,我和你介紹索引時曾經提到過,InnoDB 裏的數據都是用 B+ 樹的結構組織的。
圖 1 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 已經滿了,這時我要再插入一行數據,會怎樣呢?
圖 2 插入數據致使頁分裂
能夠看到,因爲 page A 滿了,再插入一個 ID 是 550 的數據時,就不得再也不申請一個新的頁面 page B 來保存數據了。頁分裂完成後,page A 的末尾就留下了空洞(注意:實
際上,可能不止 1 個記錄的位置是空洞)。
另外,更新索引上的值,能夠理解爲刪除一箇舊的值,再插入一個新值。不難理解,這也是會形成空洞的。
也就是說,通過大量增刪改的表,都是多是存在空洞的。因此,若是可以把這些空洞去掉,就能達到收縮表空間的目的。
而重建表,就能夠達到這樣的目的。
試想一下,若是你如今有一個表 A,須要作空間收縮,爲了把表中存在的空洞去掉,你能夠怎麼作呢?
你能夠新建一個與表 A 結構相同的表 B,而後按照主鍵 ID 遞增的順序,把數據一行一行因爲表 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 名字的來源。
我記得有同窗在第 6 篇講表鎖的文章《全局鎖和表鎖 :給表加個字段怎麼索這麼多阻礙?》的評論區留言說,DDL 以前是要拿 MDL 寫鎖的,這樣還能叫 Online DDL 嗎?
確實,圖 4 的流程中,alter 語句在啓動的時候須要獲取 MDL 寫鎖,可是這個寫鎖在真正拷貝數據以前就退化成讀鎖了。
爲何要退化呢?爲了實現 Online,MDL 讀鎖不會阻塞增刪改操做。
那爲何不乾脆直接解鎖呢?爲了保護本身,禁止其餘線程對這個表同時作 DDL。
而對於一個大表來講,Online DDL 最耗時的過程就是拷貝數據到臨時表的過程,這個步驟的執行期間能夠接受增刪改操做。因此,相對於整個 DDL 過程來講,鎖的時間很是
短。對業務來講,就能夠認爲是 Online 的。
須要補充說明的是,上述的這些重建方法都會掃描原表數據和構建臨時文件。對於很大的表來講,這個操做是很消耗 IO 和 CPU 資源的。所以,若是是線上服務,你要很當心地控
制操做時間。若是想要比較安全的操做的話,我推薦你使用 GitHub 開源的 gh-ost 來作。
說到 Online,我還要再和你澄清一下它和另外一個跟 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 的。
若是說這兩個邏輯之間的關係是什麼的話,能夠歸納爲:
1. DDL 過程若是是 Online 的,就必定是 inplace 的;
2. 反過來未必,也就是說 inplace 的 DDL,有可能不是 Online 的。截止到 MySQL8.0,添加全文索引(FULLTEXT index)和空間索引 (SPATIAL index) 就屬於這種狀況。
最後,咱們再延伸一下。
在第 10 篇文章《MySQL 爲何有時候會選錯索引》的評論區中,有同窗問到使用optimize table、analyze table 和 alter table 這三種方式重建表的區別。這裏,我順便
再簡單和你解釋一下。
今天這篇文章,我和你討論了數據庫中收縮表空間的方法。
如今你已經知道了,若是要收縮一個表,只是 delete 掉表裏面不用的數據的話,表文件的大小是不會變的,你還要經過 alter table 命令重建表,才能達到表文件變小的目的。我
跟你介紹了重建表的兩種實現方式,Online DDL 的方式是能夠考慮在業務低峯期使用的,而 MySQL 5.5 及以前的版本,這個命令是會阻塞 DML 的,這個你須要特別當心。
最後,又到了咱們的課後問題時間。
假設如今有人碰到了一個「想要收縮表空間,結果拔苗助長」的狀況,看上去是這樣的:
1. 一個表 t 文件大小爲 1TB;
2. 對這個表執行 alter table t engine=InnoDB;
3. 發現執行完成後,空間不只沒變小,還稍微大了一點兒,好比變成了 1.01TB。
你以爲多是什麼緣由呢 ?
你能夠把你以爲可能的緣由寫在留言區裏,我會在下一篇文章的末尾把你們描述的合理的緣由都列出來,之後其餘同窗就不用掉到這樣的坑裏了。感謝你的收聽,也歡迎你把這篇
文章分享給更多的朋友一塊兒閱讀。
在上期文章最後,我留給你的問題是,若是一個高配的機器,redo log 設置過小,會發生什麼狀況。
每次事務提交都要寫 redo log,若是設置過小,很快就會被寫滿,也就是下面這個圖的狀態,這個「環」將很快被寫滿,write pos 一直追着 CP。
這時候系統不得不中止全部更新,去推動 checkpoint。
這時,你看到的現象就是磁盤壓力很小,可是數據庫出現間歇性的性能下跌。
1.Truncate 會釋放表空間嗎
2.重建表的時候若是沒有數據更新,有沒有可能產生頁分裂和空洞
3.頁分裂是發生在索引仍是數據上
4.應用 row log 的過程會不會再次產生頁分裂和空洞
5.不影響增刪改,就是 Online;相對 Server層沒有新建臨時表,就是 inplace,這裏怎麼判斷是否是相對 Server 層沒有新建臨時表
辛苦老師解答一下,謝謝老師
1. Truncate 能夠理解爲drop+create
2. Online 能夠認爲沒有
3. 數據也是索引哦
4. 可能會
5. 好問題,我放到明天答疑部分
1:爲啥刪除了表的一半數8據,表文文件大小沒變化?
由於delete 命令其實只是把記錄的位置,或者數據頁標記爲了「可複用」,但磁盤文件的大小是不會變的。也能夠認爲是一種邏輯刪除,因此物理空間沒有實際釋放,
只是標記爲可複用,表文件的大小固然是不變的啦!
2:表的數據信息存在哪裏?
表數據信息可能較小也可能巨大無比,她能夠存儲在共享表空間裏,也能夠單獨存儲在一個以.ibd爲後綴的文件裏,由參數innodb_file_per_table來控制,
老師建議老是做爲一個單獨的文件來存儲,這樣很是容易管理,而且在不須要的時候,使用drop table命令也能直接把對應的文件刪除,
若是存儲在共享空間之中即便表刪除了空間也不會釋放。
3:表的結構信息存在哪裏?
首先,表結構定義佔有的存儲空間比較小,在MySQL8.0以前,表結構的定義信息存在以.frm爲後綴的文件裏,在MySQL8.0以後,
則容許把表結構的定義信息存在系統數據表之中。
系統數據表,主要用於存儲MySQL的系統數據,好比:數據字典、undo log(默認)等文件
4:如何才能刪除表數據後,表文件大小就變小?
重建表,消除表由於進行大量的增刪改操做而產生的空洞,使用以下命令:
1:alter table t engine=InnoDB
2:optimize table t( 等於 recreate+analyze)。
3:truntace table t (等於drop+create)
5:空洞是啥?咋產生的?
五、空洞就是那些被標記可複用可是還沒被使用的存儲空間。