MySQL表的碎片整理和空間回收小結


MySQL表碎片化(Table Fragmentation)的原因

 

關於MySQL中表碎片化(Table Fragmentation)產生的原因,簡單總結一下,MySQL Engine不同,碎片化的原因可能也有所差別。這里沒有深入理解、分析這些差別。此文僅以InnoDB引擎為主。總結如有不足或錯誤的地方,敬請指出。

 

InnoDB表的數據存儲在頁(page)中,每個頁可以存放多條記錄。這些記錄以樹形結構組織,這顆樹稱為B+樹索引。表中數據和輔助索引都是使用B+樹結構。維護表中所有數據的這顆B+樹索引稱為聚簇索引,通過主鍵來組織的。聚簇索引的葉子節點包含行中所有字段的值,輔助索引的葉子節點包含索引列和主鍵列。

 

在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.

 

表的數據存儲也可能碎片化。然而數據存儲的碎片化比索引更加復雜。有三種類型的數據碎片化。##下面部分內容摘自【高性能MySQL】##

 

 

行碎片(Row fragmentation)

 

這種碎片指的是數據行被存儲為多個地方的多個片段。即使查詢只從索引中訪問一行記錄。行碎片也會導致性能下降。

 

行間碎片(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

 

clip_image001

 

 

然后我們在數據庫上刪除掉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)

 

clip_image002

 

 

方法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/ 

https://yq.aliyun.com/articles/41166

http://mysql.taobao.org/monthly/2015/08/05/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM