網絡上有不少MySQL表碎片整理的問題,大多數是經過demo一個表而後參考data free來進行碎片整理,這種方式對myisam引擎或者其餘引擎可能有效(本人沒有作詳細的測試).
對Innodb引擎是否是準確的,或者data free是否是能夠參考,仍是值得商榷的。
本文基於MySQL的Innodb存儲引擎,數據庫版本是8.0.18,對碎片(fragment)作一個簡單的分析,來講明如何量化表的碎片化程度。html
涉及的參數
1,information_schema_stats_expiry
information_schema是一個基於共享表空間的虛擬數據庫,存儲的是一些系統元數據信息,某些系統表的數據並非實時更新的,具體更新是基於參數information_schema_stats_expiry。
information_schema_stats_expiry默認值是86400秒,也就是24小時,意味着24小時刷新一次information_schema中的數據,作測試的時候能夠設置爲0,實時刷新information_schema中的元數據信息。
2,innodb_fast_shutdown
由於要基於磁盤作一些統計,須要將緩存或者redo log中的數據在重啓實例的時候實時刷入磁盤,這裏設置爲0,在重啓數據庫的時候將緩存或者redo log實時寫入表的物理文件。
3,innodb_stats_persistent_sample_pages
由於涉及一些系統數據更新時對page的採樣比例,這裏設置爲一個較大的值,爲100000,儘量高比例採樣來生成系統數據。
4,innodb_flush_log_at_trx_commit sync_binlog
由於涉及大量數據的寫操做,爲加快測試,關閉double 1模式。
5,innodb_fill_factor
頁面填充率保留默認的設置,默認值是100
以上涉及的參數僅針對本測試,並不必定表明最優,同時測試過程當中(數據寫入或者刪除後)會不斷地重啓實例,以刷新相對應的物理文件。
mysql
碎片的概念
數據存儲在文件系統上的時候,老是不能100%利用分配給它的物理空間,好比刪除數據會在頁面上留下一些」空洞」,或者隨機寫入(彙集索引非線性增長)會致使頁分裂,頁分裂會致使頁面的利用空間少於50%。
另外對錶進行增刪改,包括對應的二級索引值的隨機的增刪改,都會致使數據頁面上留下一些「空洞」,雖然這些位置有可能會被重複利用,但終究會致使部分物理空間未被使用,也就是碎片。
即使是設置了填充因子爲100%,Innodb也會主動留下page頁面1/16的空間做爲預留使用(An innodb_fill_factor
setting of 100 leaves 1/16 of the space in clustered index pages free for future index growth.)。
關係數據庫的存儲結構原理上是相似的,理論上很簡單,就不過多囉嗦了。
測試表以及數據git
作個簡單的測試,表結構以下,github
CREATE TABLE `fragment_test` ( `id` INT NOT NULL AUTO_INCREMENT, `c1` INT NULL DEFAULT NULL, `c2` INT NULL DEFAULT NULL, `c3` VARCHAR(50) NULL DEFAULT NULL, `c4` DATETIME(6) NULL DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE INDEX idx_c1 ON fragment_test(c1); CREATE INDEX idx_c2 ON fragment_test(c2); CREATE INDEX idx_c3 ON fragment_test(c3);
生成200W測試數據(CALL test_insertdata(2000000);)sql
CREATE DEFINER=`root`@`%` PROCEDURE `test_insertdata`( IN `loopcount` INT ) BEGIN declare v_uuid varchar(50); while loopcount>0 do set v_uuid = uuid(); INSERT INTO fragment_test(c1,c2,c3,c4) VALUES (RAND()*200000000,RAND()*200000000,UUID(),NOW(6)); set loopcount = loopcount -1; end while; END
查詢語句,參考自最後的連接中的文章數據庫
SELECT NAME, TABLE_ROWS, UPDATE_TIME, format_bytes(data_length) DATA_SIZE, format_bytes(index_length) INDEX_SIZE, format_bytes(data_length+index_length) TOTAL_SIZE, format_bytes(data_free) DATA_FREE, format_bytes(FILE_SIZE) FILE_SIZE, format_bytes((FILE_SIZE/10 - (data_length/10 + index_length/10))*10) WASTED_SIZE FROM information_schema.TABLES as t JOIN information_schema.INNODB_TABLESPACES as it ON it.name = concat(table_schema,"/",table_name) WHERE TABLE_NAME = 'fragment_test';
碎片的測試
上面說到數據在存儲的時候,老是沒法100%利用物理存儲空間,Innodb甚至會本身主動預留一部分空閒的空間(1/16),那麼如何衡量一個表究竟有多少還沒有利用的空間?
這裏從系統表information_schema.tables和information_schema.innodb_tablespaces,來對比實際使用空間和已分配空間來對比,來間接量化碎片或者說未利用空間的程度。緩存
而後觀察數據空間的分配狀況,儘管系統表中的數據不是徹底準確的,可是也比較接近實際的200W,系統表顯示1971490,暫時拋開這一小點偏差。
能夠很清楚地看到,數據和索引的空間是329MB,文件空間是344MB,DATA_FREE空間是6MB。
ruby
隨機刪除1/4的數據,也就是50W行,而後重啓實例,並分析表(analyze table),繼續來觀察這個空間的分配(DELETE FROM fragment_test ORDER BY RAND() LIMIT 500000;)
這裏看到,
1,系統表顯示150000行,跟表中的數據徹底一致(儘管更多的時候這個值是一個大概的值,並不必定準確,嚴格說可能很是不許確,這裏歸因於innodb_stats_persistent_sample_pages的設置)。
2,數據文件空間沒有增長(344MB),能夠理解,由於這裏是刪數據操做,因此不用申請空間。
3,刪除了1/4的數據,數據和索引的的大小基本上不變,這裏就開始有疑問了,爲何沒有成比例減小?
4,data_free增長了3MB,顯然這不是跟刪除的數據成比例增長的
那麼怎麼理解碎片?DATA_FREE怎麼理解?碎片或者說可用空間又怎麼衡量?
網絡
從200W數據中隨機刪除50W,也就是1/4,表的空間沒有變化,能夠確定的是如今存在大量的碎片或者說可用空間,可是表的總的大小沒變化,data_free也基本上沒有變化到這裏就有點說不通了。
那麼data free究竟是怎麼計算的,看官方的解釋:工具
The number of allocated but unused bytes.
InnoDB tables report the free space of the tablespace to which the table belongs. For a table located in the shared tablespace, this is the free space of the shared tablespace.
If you are using multiple tablespaces and the table has its own tablespace, the free space is for only that table.
Free space means the number of bytes in completely free extents minus a safety margin. Even if free space displays as 0, it may be possible to insert rows as long as new extents need not be allocated.
data_free的計算方式或者說條件,是徹底空閒的區(extents,每一個區1MB,64個連續的16 kb 大小的page),只有一個徹底沒有使用的區,才統計爲data_free,所以data_free並不能反映出來真正的空閒空間。
同時測試中發現,performance_schema.tables中的table_rows會受到innodb_stats_persistent_sample_pages的影響,可是data_length和index_length看起來是不會受innodb_stats_persistent_sample_pages的影響的
這裏採樣比例已經足夠大,儘管table_rows已是一個徹底準確的數字了,可是data_length和index_length卻仍舊是一個偏差很是大的數字。
說到這裏,那麼這個碎片問題如何衡量?若是隻是看performance_schema.tables或者information_schema.INNODB_TABLESPACES,其實依舊是一個無解的問題,由於沒法經過這些信息,獲得一個相對準確的碎片化程度。
其實在這裏(參考連接)的評論中也提到這個問題,我是比較贊同的。
若是要真正獲得碎片程度,其實仍是須要重建表來對比實現,這裏刪除了1/4的數據,理論上就有大概1/4的可用空間,可是上面的查詢結果並不能給出一個明確的答案,怎麼驗證這個答案呢?
這裏就要粗暴地優化表了(optimize table fragment_test+analyze table),優化表只是「重整」了碎片,可是系統表的數據並無更新,所以必需要再執行一次分析表 analyze table來更新元數據信息
其實這裏也能說明,analyze table只是更新元數據,若是存儲空間沒有更新(recreated),單純地analyze table也是沒有用的。
對標進行optimize和anlayze以後,這裏能夠看到,物理空間確實減小了大概1/4的量。
這裏其實就是爲了說明一個問題:Innodb表沒法經過data free來判斷表的碎片化程度。
然而這裏(參考連接)的測試說明刪除數據後data free有明顯的變化,這個又是爲何,剛特麼說沒法經過data free來判斷表的碎片化程度,如今又說刪除數據後data free有明顯的變化???
其實(參考連接)中有另一個比較有意思的測試,相對用隨機刪除的方式,採用連續刪除的時候(或者是整個表的數據所有刪除),這個data free確實會相對準確地體現出來刪除數據後表size的變化狀況。
這又是爲何?其實不難理解,上面已經說了,data free的計算方式,是按照徹底「乾淨」的區(extent)來作統計的,
若是按照彙集索引連續的方式刪除(相對隨機刪除),那些存儲連續數據的區(extent)是能夠徹底釋放出來的,這些區的空間釋放出來以後,會被認爲是data free,因此data free此時又是相對來講準確的。
所以,不少測試,若是想到獲得客觀的數據,須要儘量多地考慮到對應的場景和測試數據狀況。
碎片的衡量
實際業務中,對標的刪除或者增刪改,不多是按照彙集索引進行批量刪除,或者說一旦存在隨機性的刪除或者更新(頁分裂),都會形成必定程度的碎片,而這個碎片化的程度是沒法經過data free來衡量的。
那麼又如何衡量這個碎片程度呢?
1,本身根據業務進行預估,在可接受程度內進行optimize table,記錄optimize table以後的table size變化程度,來衡量一個表在必定時間操做後的碎片化程度,從而來指導是否,或者多久對該表再次進行optimize table
2,採用上述鏈接中提到的innodb_ruby 這個工具,直接解析表的物理文件,這種方式相對來講更加直接。不過這個工具本人沒來得及測試,理論上是沒有問題的。
這裏盜用上述連接中的圖片,綠色的是實際使用的空間,中間的黑塊就是所謂的碎片或者說是空洞。
參考連接:
https://dev.mysql.com/doc/refman/5.7/en/innodb-file-defragmenting.html
https://dev.mysql.com/doc/refman/8.0/en/tables-table.html
https://lefred.be/content/overview-of-fragmented-mysql-innodb-tables/
https://lefred.be/content/mysql-innodb-disk-space/
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_fill_factor