MySQL表碎片化(Table Fragmentation)的緣由html
關於MySQL中表碎片化(Table Fragmentation)產生的緣由,簡單總結一下,MySQL Engine不一樣,碎片化的緣由可能也有所差異。這裏沒有深刻理解、分析這些差異。此文僅以InnoDB引擎爲主。總結若有不足或錯誤的地方,敬請指出。mysql
InnoDB表的數據存儲在頁(page)中,每一個頁能夠存放多條記錄。這些記錄以樹形結構組織,這顆樹稱爲B+樹索引。表中數據和輔助索引都是使用B+樹結構。維護表中全部數據的這顆B+樹索引稱爲聚簇索引,經過主鍵來組織的。聚簇索引的葉子節點包含行中全部字段的值,輔助索引的葉子節點包含索引列和主鍵列。sql
在InnoDB中,刪除一些行,這些行只是被標記爲「已刪除」,而不是真的從索引中物理刪除了,於是空間也沒有真的被釋放回收。InnoDB的Purge線程會異步的來清理這些沒用的索引鍵和行。可是依然沒有把這些釋放出來的空間還給操做系統從新使用,於是會致使頁面中存在不少空洞。若是表結構中包含動態長度字段,那麼這些空洞甚至可能不能被InnoDB從新用來存新的行,由於空間空間長度不足。關於這個你能夠參考博客Overview of fragmented MySQL InnoDB tables。數據庫
另外,刪除數據就會致使頁(page)中出現空白空間,大量隨機的DELETE操做,必然會在數據文件中形成不連續的空白空間。而當插入數據時,這些空白空間則會被利用起來.因而形成了數據的存儲位置不連續。物理存儲順序與邏輯上的排序順序不一樣,這種就是數據碎片。服務器
對於大量的UPDATE,也會產生文件碎片化 , Innodb的最小物理存儲分配單位是頁(page),而UPDATE也可能致使頁分裂(page split),頻繁的頁分裂,頁會變得稀疏,而且被不規則的填充,因此最終數據會有碎片。併發
First at all you must understand that Mysql tables get fragmented when a row is updated, so it's a normal situation. When a table is created, lets say imported using a dump with data, all rows are stored with no fragmentation in many fixed size pages. When you update a variable length row, the page containing this row is divided in two or more pages to store the changes, and these new two (or more) pages contains blank spaces filling the unused space.app
表的數據存儲也可能碎片化。然而數據存儲的碎片化比索引更加複雜。有三種類型的數據碎片化。##下面部份內容摘自【高性能MySQL】##dom
行碎片(Row fragmentation)異步
這種碎片指的是數據行被存儲爲多個地方的多個片斷。即便查詢只從索引中訪問一行記錄。行碎片也會致使性能降低。ide
行間碎片(Intra-row fragmentaion)
行間碎片是指邏輯上順序的頁,或者行在磁盤上不是順序存儲的。行間碎片對諸如全表掃描和聚簇索引掃描之類的操做有很大的影響,由於這些操做本來可以從磁盤上順序存儲的數據中獲益。
剩餘空間碎片(Free space fragmentation)
剩餘空間碎片是指數據頁中有大量的空餘空間。這會致使服務器讀取大量不須要的數據。從而形成浪費。
對於MyISAM表,這三類碎片化都有可能發生。但InnoDB不會出現短小的行碎片;InnoDB會移動短小的行並寫到一個片斷中。InnoDb會移動短小的行並重寫到一個片斷中。
官方文檔14.15.4 Defragmenting a Table關於下降表的碎片化介紹以下(很是簡潔,MySQL官方文檔每每簡潔,信息量大,可是沒有詳細介紹):
Random insertions into or deletions from a secondary index can cause the index to become fragmented. Fragmentation means that the physical ordering of the index pages on the disk is not close to the index ordering of the records on the pages, or that there are many unused pages in the 64-page blocks that were allocated to the index.
One symptom of fragmentation is that a table takes more space than it 「should」 take. How much that is exactly, is difficult to determine. All InnoDB data and indexes are stored in B-trees, and their fill factor may vary from 50% to 100%.
從二級索引中隨機插入或刪除可能會致使索引碎片化。碎片意味着磁盤上索引頁的物理排序不接近頁面上記錄的索引排序,或者64頁塊中有許多未使用的頁面被分配給索引。
碎片化的一個症狀是表格佔用的空間比「應該」佔用的空間多。多少確切地說,很難肯定。全部 InnoDB 數據和索引都存儲在 B-trees 中,它們的 fill factor 可能在50%到100%之間變化。碎片的另外一個症狀是像這樣的表掃描須要比「應該」花費更多的時間
MySQL中如何找出碎片化嚴重的表
關於MySQL中表碎片化,那麼如何找出MySQL中的碎片,通常有兩種方法。
方法1:使用show table status from xxxx like 'xxxx' \G;
第一個xxx:表所在的數據庫名稱,第二個xxx:要查詢的表名。這個方法其實不太實用。例如,只能單個表的查詢碎片化狀況(難道一個數據庫要一個個表去試?),不能查詢某個數據庫或整個實例下全部表的碎片化等等。這裏僅僅做爲一個參考方法而已。
mysql> create table frag_tab_myisam
-> (
-> id int,
-> name varchar(63)
-> ) engine=MyISAM;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into frag_tab_myisam
-> values(1, 'it is only test row 1');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> insert into frag_tab_myisam
-> values(2, 'it is only test row 2');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql>
mysql> insert into frag_tab_myisam
-> values(3, 'it is only test row 3');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> insert into frag_tab_myisam
-> values(4, 'it is only test row 4');
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> show table status from kkk like 'frag_tab_myisam' \G;
以下截圖所示,若是沒有DML操做,Data_free的大小是0
而後咱們在數據庫上刪除掉2條記錄,以下所示,Data_free的大小爲64KB大小了。
mysql> delete from frag_tab_myisam where id =1;
Query OK, 1 row affected (0.00 sec)
mysql> delete from frag_tab_myisam where id =3;
Query OK, 1 row affected (0.00 sec)
方法2:查詢information_schema.TABLES獲取表的碎片化信息。
以下所示,這個是我整理的一個查詢表碎片化的經典腳本。你能夠在上面作不少衍生:例如,查詢某個數據庫的表碎片化狀況。或者空閒空間超過50M大小的表。這個能夠根據本身的需求設定查詢條件。在此略過。
SELECT CONCAT(table_schema, '.', table_name) AS TABLE_NAME
,engine AS TABLE_ENGINE
,table_type AS TABLE_TYPE
,table_rows AS TABLE_ROWS
,CONCAT(ROUND(data_length / ( 1024 * 1024), 2), 'M') AS TB_DATA_SIZE
,CONCAT(ROUND(index_length / ( 1024 * 1024), 2), 'M') AS TB_IDX_SIZE
,CONCAT(ROUND((data_length + index_length )
/ ( 1024 * 1024 ), 2), 'M') AS TOTAL_SIZE
,CASE WHEN data_length =0 THEN 0
ELSE ROUND(index_length / data_length, 2) END AS TB_INDX_RATE
,CONCAT(ROUND( data_free / 1024 / 1024,2), 'MB') AS TB_DATA_FREE
,CASE WHEN (data_length + index_length) = 0 THEN 0
ELSE ROUND(data_free/(data_length + index_length),2)
END AS TB_FRAG_RATE
FROM information_schema.TABLES
ORDER BY data_free DESC;
SELECT CONCAT(table_schema, '.', table_name) AS TABLE_NAME
,engine AS TABLE_ENGINE
,table_type AS TABLE_TYPE
,table_rows AS TABLE_ROWS
,CONCAT(ROUND(data_length / ( 1024 * 1024), 2), 'M') AS TB_DATA_SIZE
,CONCAT(ROUND(index_length / ( 1024 * 1024), 2), 'M') AS TB_IDX_SIZE
,CONCAT(ROUND((data_length + index_length )
/ ( 1024 * 1024 ), 2), 'M') AS TOTAL_SIZE
,CASE WHEN data_length =0 THEN 0
ELSE ROUND(index_length / data_length, 2) END AS TB_INDX_RATE
,CONCAT(ROUND( data_free / 1024 / 1024,2), 'MB') AS TB_DATA_FREE
,CASE WHEN (data_length + index_length) = 0 THEN 0
ELSE ROUND(data_free/(data_length + index_length),2)
END AS TB_FRAG_RATE
FROM information_schema.TABLES
WHERE ROUND(DATA_FREE/1024/1024,2) >=50
ORDER BY data_free DESC;
SELECT TABLE_SCHEMA
,TABLE_NAME
,ENGINE
,ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024), 2) AS SIZE_MB
,ROUND(DATA_FREE/1024/1024,2) AS FREE_SIZ_MB
FROM information_schema.TABLES
WHERE DATA_FREE >=10*1024*1024
ORDER BY FREE_SIZ_MB DESC;
MySQL中如何減低表的碎片
在MySQL中,可使用OPTIMIZE TABLE、ALTER TABLE XXXX ENGINE = INNODB這兩種方法下降碎片,關於這二者的簡單介紹以下:
OPTIMIZE TABLE
OPTIMIZE TABLE 會重組表和索引的物理存儲,減小對存儲空間使用和提高訪問表時的IO效率。對每一個表所作的確切更改取決於該表使用的存儲引擎
OPTIMIZE TABLE的支持表類型:INNODB,MYISAM, ARCHIVE,NDB;它會重組表數據和索引的物理頁,對於減小所佔空間和在訪問表時優化IO有效果。OPTIMIZE 操做會暫時鎖住表,並且數據量越大,耗費的時間也越長。
OPTIMIZE TABLE後,表的變化跟存儲引擎有關。
對於MyISAM, PTIMIZE TABLE 的工做原理以下:
· 若是表有已刪除的行或拆分行(split rows),修復該表。
· 若是未對索引頁面進行排序,對它們進行排序。
· 若是表的統計信息不是最新的(而且沒法經過對索引進行排序來完成修復),更新它們。
英文原文以下:
For MyISAM tables, OPTIMIZE TABLE works as follows:
1. If the table has deleted or split rows, repair the table.
2. If the index pages are not sorted, sort them.
3. If the table's statistics are not up to date (and the repair could not be accomplished by sorting the index), update them.
對於InnoDB而言,PTIMIZE TABLE 的工做原理以下
對於InnoDB表, OPTIMIZE TABLE映射到ALTER TABLE ... FORCE(或者這樣翻譯:在InnoDB表中等價 ALTER TABLE ... FORCE),它重建表以更新索引統計信息並釋放聚簇索引中未使用的空間。當您在InnoDB表上運行時,它會顯示在OPTIMIZE TABLE的輸出中,以下所示:
mysql> OPTIMIZE TABLE foo;
+----------+----------+----------+-------------------------------------------------------------------+
| Table | Op | Msg_type | Msg_text |
+----------+----------+----------+-------------------------------------------------------------------+
| test.foo | optimize | note | Table does not support optimize, doing recreate + analyze instead |
| test.foo | optimize | status | OK |
+----------+----------+----------+-------------------------------------------------------------------+
OPTIMIZE TABLE對InnoDB的普通表和分區表使用online DDL,從而減小了併發DML操做的停機時間。由OPTIMIZE TABLE觸發表的重建,並在ALTER TABLE ... FORCE的掩護下完成。僅在操做的準備階段和提交階段期間短暫地進行獨佔表鎖定。在準備階段,更新元數據並建立中間表。在提交階段,將提交表元數據更改。
OPTIMIZE TABLE 在如下條件下使用表複製方法重建表:
o 啓用old_alter_table系統變量時。
o 啓用mysqld --skip-new 選項時。
OPTIMIZE TABLE 對於包含FULLTEXT索引的InnoDB表不支持online DDL。而是使用複製表的方法。
InnoDB使用頁面分配方法存儲數據,而且不會像傳統存儲引擎(例如MyISAM)那樣受到碎片的影響。在考慮是否運行優化時,請考慮服務器將處理的事務的工做負載:
o 預計會有必定程度的碎片化。 InnoDB僅填充93%的頁面,爲更新留出空間而無需拆分頁面。
o 刪除操做可能會留下空白,使頁面填充不如預期,這可能使得優化表格變得有價值。
當行有足夠的空間時,對行的更新一般會重寫同一頁面中的數據,具體取決於數據類型和行格式。見 Section 14.9.1.5, 「How Compression Works for InnoDB Tables」 和 Section 14.11, 「InnoDB Row Formats」 。
高併發工做負載可能會隨着時間的推移在索引中留下空白,由於InnoDB經過其MVCC機制保留了相同數據的多個版本。見 Section 14.3, 「InnoDB Multi-Versioning」 。
另外,對於innodb_file_per_table=1的InnoDB表,OPTIMIZE TABLE 會重組表和索引的物理存儲,將空閒空間釋放給操做系統。也就是說OPTIMIZE TABLE [tablename] 這種方式只適用於獨立表空間
關於OPTIMIZE TABLE,更多詳細細節參考https://dev.mysql.com/doc/refman/8.0/en/optimize-table.html。感受官方文檔至關詳細。
ALTER TABLE table_name ENGINE = Innodb;
這實際上是一個NULL操做,表面上看什麼也不作,實際上從新整理碎片了.當執行優化操做時,實際執行的是一個空的 ALTER 命令,可是這個命令也會起到優化的做用,它會重建整個表,刪掉未使用的空白空間.
Running ALTER TABLE tbl_name ENGINE=INNODB on an existing InnoDB table performs a 「null」 ALTER TABLE operation, which can be used to defragment an InnoDB table, as described in Section 15.11.4, 「Defragmenting a Table」. Running ALTER TABLE tbl_name FORCE on an InnoDB table performs the same function.
問題1:那麼是用OPTIMIZE TABLE 仍是ALTER TABLE xxxx ENGINE= INNODB好呢?
其實對於InnoDB引擎,ALTER TABLE xxxx ENGINE= INNODB是執行了一個空的ALTER TABLE操做。而OPTIMIZE TABLE等價於ALTER TABLE ... FORCE。 參考上面描述,在有些狀況下,OPTIMIZE TABLE 仍是ALTER TABLE xxxx ENGINE= INNODB基本上是同樣的。可是在有些狀況下,ALTER TABLE xxxx ENGINE= INNODB更好。例如old_alter_table系統變量沒有啓用等等。另外對於MyISAM類型表,使用ALTER TABLE xxxx ENGINE= INNODB是明顯要優於OPTIMIZE TABLE這種方法的。
問題2:ALTER TABLE xxxx ENGINE= INNODB 表上的索引碎片會整理麼
ALTER TABLE ENGINE= INNODB,會從新整理在聚簇索引上的數據和索引。若是你想用實驗驗證,能夠對比執行該命令先後index_length的大小。
其它工具
網友建議使用pt工具或者gh-ost下降表的碎片化,我的暫時尚未使用過這類工具,估計也是封裝了上面兩個命令。此處不作展開介紹。
參考資料:
【高性能MySQL】
https://dev.mysql.com/doc/refman/8.0/en/optimize-table.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-file-defragmenting.html
https://lefred.be/content/overview-of-fragmented-mysql-innodb-tables/