Mysql壓縮解決方案
提到mysql壓縮相關的內容,我們能想到的可能是如下幾種和壓縮相關的場景:
1、客戶端和伺服器之間傳輸的資料量太大,需要進行壓縮,節約頻寬
2、mysql某個列的資料量大,只針對某個列的資料壓縮
3、mysql某個或者某幾個表資料太多,需要將表資料壓縮存放,減少磁碟空間的佔用
這幾個問題在mysql側都有很好的解決方案 ,針對第1個問題,可以使用mysql的壓縮協議解決;針對第2個問題,可以採用mysql的壓縮和解壓函式完美解決;而針對最複雜的第3個問題,則可以在引擎層面進行解決,目前myisam、innodb、tokudb、MyRocks等引擎都支援表的壓縮。本篇文章要詳細討論的就是此類關於mysql壓縮機制相關 的問題,下面是主要的內容:
一、mysql壓縮協議介紹
1、適用場景
mysql壓縮協議適合的場景是mysql的伺服器端和客戶端之間傳輸的資料量很大,或者可用頻寬不高的情況,典型的場景有如下兩個:
a、查詢大量的資料,頻寬不夠(比如匯出資料的時候)
b、複製的時候binlog量太大,啟用slave_compressed_protocol引數進行日誌壓縮複製
2、壓縮協議簡介
壓縮協議是mysql通訊協議的一部分,要啟用壓縮協議進行資料傳輸,需要mysql伺服器端和客戶端都支援zlib演算法。啟動壓縮協議會導致CPU負載略微上升。使用啟用壓縮協議使用-C引數或者 --compress=true引數啟動客戶端的壓縮功能。如果啟用了-C或者compress=true選項,那麼在連線到伺服器段的時候,會發送0x0020(CLIENT_COMPRESS)的伺服器權能標誌位,和伺服器端協商通過後(3次握手以後),就支援壓縮協議了。由於採用壓縮,資料包的格式會發生變化,具體的變化如下:
未壓縮的資料包格式:
壓縮後的資料包格式:
大家可能留意到壓縮後的資料報格式有壓縮和未壓縮之分,這個是mysql為了較少CPU開銷而做的一個優化。如果內容小於50個位元組的時候,就不對內容進行壓縮,而大於50位元組的時候,才會啟用壓縮功能。具體的規則如下:
當第三個欄位的值等於0x00的時候,表示當前包沒有壓縮,因此n*byte的內容為1*byte,n*byte,即請求型別和請求內容。
當第三個欄位的值大於0x00的時候,表示當前包已採用zlib壓縮,因此使用的時候需要對n*byte進行解壓,解壓後內容為1*byte,n*byte,即請求型別和請求內容。
3、方案實踐
在客戶端連線的時候加上-C或者--compress=true引數。如果是對同步新增壓縮協議支援的時候,則需要配置slave_compressed_protocol=1。下面是採用壓縮協議連線mysql服務端的範例:
mysql -h hostip -uroot -p password --compress
mysqldump -h hostip -uroot -p password -default-character-set=utf8 --compress --single-transaction dbname tablename > tablename.sql
如果需要在主從複製中啟用壓縮傳輸,則在從機開啟slave_compressed_protocol=1引數就OK。
4、壓縮效果
可以通過在mysqldump中使用--compress選項來觀察壓縮傳輸的效果,也可以通過主從複製中已用slave_compressed_protocol引數來觀察壓縮傳輸的效果,很容易看出效果,這裡不再截圖說明。
二、mysql列壓縮解決方案
mysql針對列的壓縮目前直接的方案並不支援,映象中騰訊的Tmysql可以直接針對列的壓縮。這裡主要介紹一個曲線救國的辦法,那就是在業務層面使用mysql提供的壓縮和解壓函式來針對列進行壓縮和解壓操作。也就是要對某一列做壓縮,就需要在寫入的時候呼叫COMPRESS函式對那個列的內容進行壓縮,然後存放到對應的列。讀取的時候,使用UNCOMPRESSED函式對壓縮的內容進行解壓縮。
1、適用場景
針對mysql中某個列或者某幾個列資料量特別大,一般都是varchar、text、char等資料型別。
2、壓縮函式簡介
mysql的壓縮函式COMPRESS壓縮一個字串,然後返回一個二進位制串。使用該函式需要mysql服務端支援壓縮,否則會返回NULL,壓縮欄位最好採用varbinary或者blob欄位型別儲存。使用UNCOMPRESSED函式對壓縮過的資料進行解壓。注意,採用這種方式需要在業務側做少量改造。壓縮後的內容儲存方式如下:
a、空字串就以空字串儲存
b、非空字串儲存方式為前4個bype儲存未壓縮的字串,緊接著儲存壓縮的字串
3、方案實踐
欄位壓縮方案涉及到的幾個相關的函式如下:
壓縮函式
COMPRESS()
解壓縮函式
UNCOMPRESS()
字串長度函式
LENGTH()
未解壓字串長度函式
UNCOMPRESSED_LENGTH()
實踐步驟:
a、建立一張測試表
CREATE TABLE IF NOT EXISTS `test`.`test_compress` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`content` blob NOT NULL COMMENT '內容列',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='壓縮測試表';
b、網表中插入壓縮的資料
insert into `test`.`test_compress`(content) values(COMPRESS(REPEAT('a',1000)));
c、讀取壓縮的資料
select UNCOMPRESS(content) from `test`.`test_compress`;
d、查詢對應的長度和內容
SELECT UNCOMPRESSED_LENGTH(content) AS length, LENGTH(content) AS compress_length, UNCOMPRESS(content), content FROM `test`.`test_compress`
4、壓縮效果
從上面截圖可以看出壓縮效果比較好,針對text、char、varchr、blob等,如果裡面重複的資料越多壓縮效果就越好。
三、InnoDB表壓縮方案解決方案
1、適用場景
採用壓縮表一般都用在由於資料量太大,磁碟空間不足,負載主要體現在IO上,而伺服器的CPU又有比較多的餘量的場景。
2、表壓縮簡介
a、為什麼需要壓縮
目前很多表都支援壓縮,比如Myisam、InnoDB、TokuDB、MyRocks 。由於使用InnoDB主要是不需要做什麼改動,對線上完全透明,壓縮方案也非常成熟,因此這裡只對InnoDB做詳細說明。對於TokuDB和MyRocks的壓縮方案講在mysql的壓縮方案<二>中撰文說明。
在SSD沒有大量橫行的時候,資料庫幾乎都是IO負載型的,在CPU有大量餘量的時候,磁碟IO的瓶頸就已經凸顯出來。而資料的大量儲存,尤其是日誌型資料和監控型別的資料,會導致磁碟空間快速增長。硬碟不夠用也會在很多業務中凸顯出來。一種比較好的方式就誕生了,那就是通過犧牲少量CPU資源,採用壓縮來減少磁碟空間佔用,以及優化IO和頻寬。尤其針對讀多些少的業務。
SSD出來後,資料庫的IO負載有所降低,但是對於磁碟空間的問題還是沒有很好的解決。因此壓縮表使用還是非常的廣泛。這也就是為什麼那麼多的引擎都支援壓縮的原因。而innodb在mysql 5.5的時候就支援了壓縮功能,只是壓縮比比較低,通常在50%左右。而tokuDB能達到80%左右,MyRocks的壓縮比能達到70%左右。
注意:壓縮比和你儲存的資料組成有很大的關係,並不是所有的資料都能達到上面所說的壓縮比。如果大部分都是字串,並且重複的資料比較多,壓縮比會很好。
b、innodb的壓縮介紹
使用innodb壓縮的前提條件是,innodb_file_per_table這個引數要啟用,innodb_file_format這個引數設定成Barracuda。
你可以使用ROW_FORMAT=COMPRESSED來create或者alter表來開啟innodb的壓縮功能,如果沒有指定KEY_BLOCK_SIZE的大小,預設KEY_BLOCK_SIZE為innodb_page_size大小的一半,也可以通過指定KEY_BLOCK_SIZE=n引數來開啟innodb的壓縮功能,n可以為1、2、4、8、16,單位是K。n的值越小,壓縮比越高,消耗的CPU資源也越多。注意32K或者64K的頁不支援壓縮。啟用壓縮後,索引資料也同樣會被壓縮。
你也可以通過調整innodb_compression_level來設定壓縮的級別,級別從1~9,預設是6。級別越低,意味著壓縮比越高,同時也意味著需要更多的CPU資源。
c、壓縮演算法
innodb壓縮藉助的是著名的zlib庫,採用L777壓縮演算法,這種演算法在減少資料大小和CPU利用方面很成熟高效。同時這種演算法是無損的,因此原生的未壓縮的資料總是能夠從壓縮檔案中重構,LZ777實現原理是查詢重複資料的序列號然後進行壓縮,所以資料模式決定了壓縮效率,一般而言,使用者的資料能夠被壓縮50%以上。
d、壓縮表在buffer_pool中如何處理
在buffer_pool緩衝池中,壓縮的資料通過KEY_BLOCK_SIZE的大小的頁來儲存,如果要提取壓縮的資料或者要更新壓縮資料對應的列,則會建立一個未壓縮頁來解壓縮資料,然後在資料更新完成後,會將為壓縮頁的資料重新寫入到壓縮頁中。記憶體不足的時候,mysql會講對應的未壓縮頁踢出去。因此如果你啟用了壓縮功能,你的buffer_pool緩衝池中可能會存在壓縮頁和未壓縮頁,也可能只存在壓縮頁。不過可能仍然需要將你的buffer_pool緩衝池調大,以便能同時能儲存壓縮頁和未壓縮頁。
mysql採用最少使用(LRU)演算法來確定將哪些頁保留在記憶體中,哪些頁剔除出去,因此熱資料會更多地保留在記憶體中。當壓縮表被訪問的時候,mysql使用自適應的LRU演算法來維持記憶體中壓縮頁和非壓縮頁的平衡。當系統IO負載比較高的時候,這種演算法傾向於講未壓縮的頁剔除,一面騰出更多的空間來存放更多的壓縮頁。當系統CPU負載比較高的時候,mysql傾向於將壓縮頁和未壓縮頁都剔除出去,這個時候更多的記憶體用來保留熱的資料,從而減少解壓的操作。
e、如何評估KEY_BLOCK_SIZE是否合適
為了更深入地瞭解壓縮表對效能的影響,在Information Schema庫中有對應的表可以用來評估記憶體的使用和壓縮率等指標。INNODB_CMP是收集的是某一類的KEY_BLOCK_SIZE壓縮表的整體狀況的資訊,彙總的是所有KEY_BLOCK_SIZE壓縮表的統計。而INNODB_CMP_PER_INDEX表則是收集各個表和索引的壓縮情況資訊,這些資訊對於在某個時間評估某個表的壓縮效率或者診斷效能問題很有幫助。INNODB_CMP_PER_INDEX表的收集會導致系統性能受到影響,必須innodb_cmp_per_index_enabled選項才會記錄,生產環境最好不要開啟。
我們可以通過觀察INNODB_CMP表的壓縮失敗情況,如果失敗比較多,則需要調大KEY_BLOCK_SIZE。一般建議KEY_BLOCK_SIZE設定為8。
3、方案實踐
a、設定好innodb_file_per_table和innodb_file_format引數
SET GLOBAL innodb_file_per_table=1;SET GLOBAL innodb_file_format=Barracuda;
b、建立對應的壓縮表
CREATE TABLE compress_test (c1 INT PRIMARY KEY,content varchar(255)) ROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE=8;
如果是已經存在的表,則通過alter來修改,SQL如下:
ALTER TABLEcompress_testROW_FORMAT=COMPRESSEDKEY_BLOCK_SIZE=8;
4、壓縮效果
壓縮效果通過線上的一個監控的表修改為壓縮後的檔案大小來說明,壓縮前後對比如下:
四、參考文獻
https://dev.mysql.com/doc/refman/5.7/en/innodb-compression-background.html
https://dev.mysql.com/doc/refman/5.7/en/general-tablespaces.html#general-tablespaces-creating
http://www.tocker.ca/2013/10/31/benchmarking-innodb-page-compression-performance.html
http://www.cnblogs.com/mysql-dba/p/5125220.html
http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/#11
https://my.oschina.net/tangcoffee/blog/362382
http://www.cnblogs.com/lispking/p/3604063.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-table-compression.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-compression-internals.html
作者:飛鴻無痕
連結:https://www.jianshu.com/p/d7cc90218222
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。