天天看點

InnoDB記錄壓縮及使用分析

此文已由作者溫正湖授權網易雲社群釋出。

歡迎通路網易雲社群,了解更多網易技術産品營運經驗。

這篇文章,源于RDS組内的一次飯後閑聊,兩位小夥伴在探讨InnoDB啟用壓縮後的種種,比如在磁盤上是怎麼存放資料的,資料頁的大小是多少?怎麼知道一個頁裡面可以寫入多少壓縮前的資料等等。兩年前曾看過InnoDB壓縮的文檔和代碼,現在好多東西都模糊了,趁此機會,又重溫了一把,下面通過問答的方式來闡述。

壓縮幹嘛用呢?直白點,就是為了省空間。假如你的手機是16G版iphone 6s,那麼你肯定明白可用的存儲空間是多麼寶貴。如果能夠把10G的資料變成5G甚至2G,而且還能不丢資訊,那麼你是不是樂翻了,做夢都在笑吧。好了,壓縮就是幹這事兒了。當然在iphone上壓縮這招一般是行不通的,不信你試試。

壓縮是百利而無一害的嗎?騷年,如果有這種想法,那真是too young too simple了。雖然壓縮有可能節省存儲空間,但也是有代價的,壓縮是要消耗cpu資源的,甚至是過多的cpu資源。目前已知的壓縮算法有很多種,不同算法的壓縮比和cpu消耗也是不一樣,常用的算法中zlib的壓縮比較高,但cpu消耗也偏高,早期程式一般采用zlib較多,如InnoDB的壓縮等。snappy和quicklz在壓縮比和cpu消耗上儲存了較好的平衡,這兩種算法在新開發的軟體中使用更為廣泛,包括TokuDB、MongoDB等。當然,如果你選擇JPG等已經壓縮過的圖檔進行壓縮,那就是no zuo no die了,誰都救不了你。

什麼樣的資料适合做壓縮呢?我們拿MySQL表作為例子,通常情況下表結構中包含字元型資料列如char, varchar, text或blob等時,具有較高的壓縮率,而一些二進制資料,如整形或浮點型資料列,或者一些已經壓縮的多媒體文檔,如jpeg、jpg、png等格式圖檔及mp4、avi等格式視訊,其壓縮率都不會好。你可以從一個非壓縮表中拷貝資料到一個相同的壓縮表中,觀察資料大小,來決定是否适合壓縮。

假如我的MySQL執行個體的表中就存在較多字元型資料列,我想啟用壓縮,怎麼整?不急,給你寶典,自己去練。首先你的MySQL版本必須高于5.1,現在都已經進入5.7時代了,這個不成問題。接下來把innodb_file_per_table設定為1,即除了系統表外,其他表都是單獨存一個檔案,還需要把innodb_file_format設定為Barracuda。然後在建新表或修改現有表的語句中加入row_format=compressed key_block_size=8就可以了。你可以僅加入row_format=compressed,這樣key_block_size就取預設值8KB了。你也可以僅加入key_block_size={1/2/4/8/16},也會預設開啟壓縮。那麼,key_block_size到底設定為多大才合理呢,這是一個深奧的問題,官方建議如下:

To determine the best value for KEY_BLOCK_SIZE, typically you create several copies of the same table with different values for this clause, then measure the size of the resulting .ibd files and see how well each performs with a realistic workload。

我的MySQL業務類型會對壓縮效益有影響嗎?為了盡量避免壓縮、解壓帶來的額外消耗,InnoDB在壓縮頁中新增了modification log區,通過記錄目前的頁的修改日志,來避免頻繁壓縮解壓。不同的業務場景,會對壓縮效益産生不同的影響,下面幾種場景相對适用壓縮:如果業務中查詢占了絕大部分,隻有很少的更新,那麼隻有較少的頁中會出現modification log空間不足,進而需要重組或者重新壓縮,這種場景下是個不錯的選擇;如果應用隻是單純的插入,此時資料的插入按照主鍵遞增的方式組織,即使存在二級索引也基本上不需要頻繁重組和重壓縮;對于删除操作,由于InnoDB删除行采用打标志位的方式來删除,對記錄的删除是通過修改頁中沒有被壓縮的中繼資料的方式實作,是以效率很高;對于更新操作,如果不是對索引列或者存儲在off-page的blob,text,長字元串的列的更新,這種場景下使用壓縮也是可以接受的。

以上說了那麼多,壓縮最終還是通過消耗更多的cpu資源來換取減少IO消耗,最終帶來性能的提升,如果應用是IO密集型,而不是cpu密集型,那麼可以利用剩餘的cpu來提升應用性能。

InnoDB的壓縮是怎麼實作的呢?其實,截止目前InnoDB已經包括兩種壓縮模式,一種是這裡提到的對記錄(包括索引)的壓縮。該壓縮從MySQL 5.1開始既已存在;另一種是在MySQL 5.7中加入的,是對資料塊的壓縮,本文不對其做介紹。

這裡對記錄壓縮做簡單介紹:InnoDB啟用壓縮後,btr_page_create會調用page_create_zip從磁盤配置設定一張空的大小為zip_size(即key_block_size)的壓縮頁,該頁在buffer pool(bp)中駐留,不斷接收插入及更新等操作的資料,InnoDB的flush或checkpoint機制将該頁資料寫回磁盤,在回刷前會調用page_zip_compress進行壓縮後,其實也不會每次寫入磁盤都會進行壓縮,就像之前問答中已經提到的,InnoDB壓縮頁中開辟有modification log區,壓縮頁中一段時間内的記錄增删改會先寫入該區,再通過一定條件觸發合并,合并的過程就是将原來的壓縮資料解壓,再将modification log區的内容合并掉,重新進行壓縮。随着資料的不斷寫入,到了一個時間點,總會出現壓縮後的資料大小仍超過了設定的key_block_size,這種場景下會觸發壓縮頁的分裂,此時就需要配置設定一個新頁,将原頁部分資料移到新頁中,再分别進行壓縮。如此,周而複始。

在記憶體充足的情況下,InnoDB bp中包括壓縮頁的壓縮狀态(buf_page_t*)page->zip.data和解壓狀态(buf_block_t*)page->frame。當記憶體不足時,InnoDB可能将解壓的frame回收,保留壓縮的zip.data在bp中。如果一個壓縮頁在一段時間内沒有被使用,壓縮格式的zip.data也會被寫回到磁盤中,以釋放記憶體。

Innodb使用一個自适應 LRU算法來維持bf内zip.data和frame的平衡,目的是為了避免在cpu繁忙時減少解壓頁的開銷,當cpu富餘時避免過多的IO。當系統是IO密集型時,傾向于回收frame,以留下記憶體給其他從磁盤讀入的頁。當cpu密集型時,InnoDB選擇全部回收zip.data和frame,也就是回收整個壓縮頁,這樣記憶體可以更多的留給熱點資料,并減少記憶體中需要解壓的頁。

最後,如何逼格更高得使用InnoDB壓縮呢? InnoDB一如既往得友好,information_schema中提供了兩組系統表來檢視運作時的壓縮行為。

系統表innodb_cmp和innodb_cmp_reset用來分析運作中的壓縮狀态,包括壓縮頁的壓縮/解壓次數,所花費時間,壓縮成功次數以及壓縮頁的大小等。其中innodb_cmp儲存曆史彙總資料,而innodb_cmp_reset則記錄的是一個較為實時的統計值,表結構如下:

mysql> show create table innodb_cmp\G

*************************** 1. row ***************************

Table: INNODB_CMP
Create Table: CREATE TEMPORARY TABLE `INNODB_CMP` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `compress_ops` int(11) NOT NULL DEFAULT '0',
  `compress_ops_ok` int(11) NOT NULL DEFAULT '0',
  `compress_time` int(11) NOT NULL DEFAULT '0',
  `uncompress_ops` int(11) NOT NULL DEFAULT '0',
  `uncompress_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql> show create table innodb_cmp_reset\G      
Table: INNODB_CMP_RESET
Create Table: CREATE TEMPORARY TABLE `INNODB_CMP_RESET` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `compress_ops` int(11) NOT NULL DEFAULT '0',
  `compress_ops_ok` int(11) NOT NULL DEFAULT '0',
  `compress_time` int(11) NOT NULL DEFAULT '0',
  `uncompress_ops` int(11) NOT NULL DEFAULT '0',
  `uncompress_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)      

如果compress_ops_ok/compress_ops的比率很高的話,則表明系統運作的良好,反之則表明InnoDB經常進行reorganize, recompress和split,此時對該表禁用壓縮或者選擇更大的key_block_size。

InnoDB bp使用夥伴配置設定系統(buddy allocator)來配置設定不同大小的記憶體頁用來緩存壓縮資料:1KB到16KB(即對應不同的key_block_size),information_schema為此提供了innodb_cmpmem和innodb_cmpmem_reset來記錄壓縮頁使用bp的一些資訊,包括:bp為壓縮頁配置設定的正在使用的頁數,可供使用的空閑頁數,夥伴系統重新配置設定頁的次數及時間等,詳細表結構如下:

mysql> show create table innodb_cmpmem\G

Table: INNODB_CMPMEM
Create Table: CREATE TEMPORARY TABLE `INNODB_CMPMEM` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `buffer_pool_instance` int(11) NOT NULL DEFAULT '0',
  `pages_used` int(11) NOT NULL DEFAULT '0',
  `pages_free` int(11) NOT NULL DEFAULT '0',
  `relocation_ops` bigint(21) NOT NULL DEFAULT '0',
  `relocation_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
 
mysql> show create table innodb_cmpmem_reset\G      
Table: INNODB_CMPMEM_RESET
Create Table: CREATE TEMPORARY TABLE `INNODB_CMPMEM_RESET` (
  `page_size` int(5) NOT NULL DEFAULT '0',
  `buffer_pool_instance` int(11) NOT NULL DEFAULT '0',
  `pages_used` int(11) NOT NULL DEFAULT '0',
  `pages_free` int(11) NOT NULL DEFAULT '0',
  `relocation_ops` bigint(21) NOT NULL DEFAULT '0',
  `relocation_time` int(11) NOT NULL DEFAULT '0'
) ENGINE=MEMORY DEFAULT CHARSET=utf8
1 row in set (0.00 sec)      

在使用InnoDB壓縮時,周期性觀察information_schema中提供的這兩組壓縮相關的臨時表,對于評估壓縮的效果和性能非常有參考價值。

網易雲免費體驗館,0成本體驗20+款雲産品! 

更多網易技術、産品、營運經驗分享請點選。

相關文章:

【推薦】 Shadowsocks原理詳解(下篇)

【推薦】 BRVAH(讓RecyclerView變得更高效)(1)

【推薦】 上雲、微服務化和DevOps,少走彎路的辦法

繼續閱讀