爲何MySQL分庫分表後總存儲大小變大了?

【MySQL系列相關】mysql

1.聊一聊關於MySQL的count(*)面試

1.背景

在完成一個分表項目後,發現分表的數據遷移後,新庫所需的存儲容量遠大於本來兩張表的大小。在作了一番查詢瞭解後,完成了優化。算法

回過頭來,須要進一步瞭解下爲何會出現這樣的狀況。sql

與標題的問題的相似問題還有,爲何表數據內容刪除了而表大小沒有變化。其本質都是同樣的。數據庫

要回答這些問題,咱們須要從mysql的索引模型談起。post

爲何MySQL分庫分表後總存儲大小變大了?

2.InnoDB 的索引模型

因爲 InnoDB 存儲引擎在 MySQL 數據庫中使用最爲普遍,因此接下來就以 InnoDB 爲例,分析其中的索引模型。性能

在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。而InnoDB中,使用了 B+ 樹索引模型,因此數據都是存儲在 B+ 樹中的,每個索引會對應一顆B+樹。優化

假設,咱們有一個主鍵列爲 ID 的表,表中有字段 k,而且在 k 上有索引,建表語句以下線程

CREATE TABLE `t` (日誌

`id` int(11) NOT NULL,

`k` int(11) NOT NULL,

`name` varchar(16) DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `k` (`k`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

爲何MySQL分庫分表後總存儲大小變大了?

表中 R1~R5 的 (ID,k) 值分別爲 (10,1)、(20,2)、(30,3)、(50,5) 和 (70,7),索引id和索引k的B+樹的示例示意圖以下。

根據葉子節點的內容,索引類型分爲主鍵索引和非主鍵索引,主鍵索引的葉子節點存的是整行數據R1~R5,非主鍵索引的葉子節點內容是主鍵的值。

從圖中能夠看出,基於非主鍵索引的查詢須要多掃描一棵索引樹才能找到對應的數據。

提一句題外話,咱們在應用中應該儘可能使用主鍵查詢

3.索引維護

B+ 樹爲了維護索引有序性,在增刪改數據的時候須要作必要的維護。

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

若是刪掉了一個數據頁上的全部記錄,那麼整個數據頁就能被複用了。進一步地,若是咱們用 delete 命令把整個表的數據刪除呢?結果就是,這個表相關的全部的數據頁都會被標記爲可複用。

可是,不管如何,磁盤文件的大小並不會縮小。

這些被標記爲可複用,而並無實際被使用的空間,就是一些「存儲空洞」。

爲何MySQL分庫分表後總存儲大小變大了?

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

以上圖爲例,若是插入新的行 ID 值爲 80,則只須要在 R5 的記錄後面插入一個新記錄。

若是新插入的 ID 值爲 60,就相對麻煩了,須要邏輯上挪動後面的數據,空出位置。

而更糟的狀況是,若是 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候須要申請一個新的數據頁,而後挪動部分數據過去。這個過程稱爲頁分裂。在這種狀況下,性能天然會受影響。

除了性能外,頁分裂操做還影響數據頁的利用率。本來放在一個頁的數據,如今分到兩個頁中,插入一條記錄居然使得總體空間利用率下降大約 50%。

能夠看到,因爲 page 2 滿了,再插入一個 ID 是 60 的數據時,就不得再也不申請一個新的頁面 page 3 來保存數據了。

頁分裂完成後,page 2 的末尾就留下了空洞(注意:實際上,可能不止 1 個記錄的位置是空洞)。

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

所以,大量的增刪改以後的表,都是可能存在很大的「數據空洞」的。

所以,咱們就能解釋,爲何分表後的總存儲變大了。
由於分表後,須要從老庫全量同步數據到新庫,數據同步平臺開啓多個線程進行同步,插入各個分表並非按照遞增的順序插入的,所以,會產生巨量的「數據空洞」,形成存儲空間變大。

若是可以把這些空洞去掉,就能達到收縮表空間的目的。而重建表就能達到這樣的目的。

4.重建表

若是咱們手動重建一張表,能夠新建一個與表 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 會自動完成轉存數據、交換表名、刪除舊錶的操做。顯然,花時間最多的步驟是往臨時表插入數據的過程,若是在這個過程當中,有新的數據要寫入到表 A 的話,就會形成數據丟失。所以,在整個 DDL 過程當中,表 A 中不能有更新。也就是說,這個 DDL 不是 Online 的。

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

  • 創建一個臨時文件,掃描表 A 主鍵的全部數據頁;
  • 用數據頁中表 A 的記錄生成 B+ 樹,存儲到臨時文件中;
  • 生成臨時文件的過程當中,將全部對 A 的操做記錄在一個日誌文件(row log)中;
  • 臨時文件生成後,將日誌文件中的操做應用到臨時文件,獲得一個邏輯數據上與表 A 相同的數據文件;(應用row log的過程可能又回有頁分裂)
  • 用臨時文件替換表 A 的數據文件。

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

須要補充說明的是,上述的這些重建方法都會掃描原表數據和構建臨時文件。對於很大的表來講,這個操做是很消耗 IO 和 CPU 資源的。所以,若是是線上服務,你要很當心地控制操做時間。

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。


參考文獻:

《MySQL實戰45講》

《MySQL技術內幕》


都看到最後了,原創不易,點個關注,點個贊吧~
掃碼關注個人公衆號「阿丸筆記」,第一時間獲取最新更新。同時能免費獲取海量Java技術棧電子書、各個大廠面試題哦。

相關文章
相關標籤/搜索