最近在數據庫優化的時候,看到一些表在設計上使用了text或者blob的字段,單表的存儲空間已經達到了近100G,這種狀況再去改變和優化就很是難了html
爲了清楚大字段對性能的影響,咱們必需要知道innodb存儲引擎的處理方式:mysql
1.1 在InnoDB 1.0.x版本以前,InnoDB 存儲引擎提供了 Compact
和 Redundant(Redundant 格式是爲兼容以前版本而保留的)
兩種格式來存放行記錄數據,compact 和 redundant 合稱爲Antelope (羚羊)
算法
對於blob,text,varchar(5120)這樣的大字段,innodb只會存放前768字節在數據頁中,而剩餘的數據則會存儲在溢出段中(發生溢出狀況的時候適用),最大768字節的做用是便於建立前綴索引/prefix index,其他更多的內容存儲在額外的page裏,哪怕只是多了一個字節。所以,全部列長度越短越好sql
1.2 MySQL 5.1 中的 innodb_plugin 引入了新的文件格式:Barracuda (梭子魚)
,該文件格式擁有新的兩種行格式:compressed
和dynamic,兩種格式對blob字段採用徹底溢出的方式,數據頁中只存放20字節,其他的都存放在溢出段中,所以,強烈不建議使用BLOB、TEXT、超過255長度的VARCHAR列類型;
數據庫
1.3 innodb的page大小默認爲16kb,innodb存儲引擎表爲索引組織表,樹底層的葉子節點爲一雙向鏈表,所以每一個頁中至少應該有兩行記錄,這就決定了innodb在存儲一行數據的時候不可以超過8k,但事實上應該更小,由於還有一些InnoDB內部數據結構要存儲,5.6版本之後,新增選項 innodb_page_size 能夠修改,在5.6之前的版本,只能修改源碼從新編譯,但並不推薦修改這個配置緩存
1.4 InnoDB的data page在有新數據寫入時,會預留1/16的空間,預留出來的空間可用於後續的新紀錄寫入,減小頻繁的新增data page的開銷,受限於InnoDB存儲方式,數據若是是順序寫入的話,最理想的狀況下,data page的填充率是15/16,但通常沒辦法保證徹底的順序寫入,所以data page的填充率通常是1/2到15/16。所以每一個InnoDB表都最好要有一個自增列做爲主鍵,使得新紀錄寫入儘量是順序的;當data page填充率不足1/2時,InnoDB會進行收縮,釋放空閒空間bash
1.5 COMPACT行格式相比REDUNDANT,大概能節省20%的存儲空間,COMPRESSED相比COMPACT大概能節省50%的存儲空間,但會致使TPS降低了90%。所以強烈不推薦使用COMPRESSED行格式數據結構
1.6 使用了blob數據類型,是否是必定就會存放在溢出段中?一般咱們認爲blob這類的大對象的存儲會把數據存放在數據頁以外,其實否則,關鍵點仍是要看一個page中到底可否存放兩行數據,blob能夠徹底存放在數據頁中(單行長度沒有超過8096字節),而varchar類型的也有可能存放在溢出頁中(單行長度超過8096字節,前768字節存放在數據頁中)性能
1.7 mysql在操做數據的時候,以page爲單位,不論是更新,插入,刪除一行數據,都須要將那行數據所在的page讀到內存中,而後在進行操做,這樣就存在一個命中率的問題,若是一個page中可以相對的存放足夠多的行,那麼命中率就會相對高一些,性能就會有提高優化
1.8 在off-page中存儲的BLOB、TEXT或者長VARCHAR列的page是獨享的,不能共享。所以強烈不建議在一個表中使用多個長列
1.9 MySQL 5.6 中默認仍是 Compact 行格式,也是目前使用最多的一種 ROW FORMAT。用戶能夠經過命令 SHOW TABLE STATUS LIKE'table_name'
來查看當前表使用的行格式,其中 row_format 列表示當前所使用的行記錄結構類型
mysql>desc db_page; +-----------------+----------------+----------------+---------------+-------------------+-----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+----------------+----------------+---------------+-------------------+-----------------+ | id | int(11) | NO | PRI | | auto_increment | | title | varchar(100) | NO | | | | | name | varchar(100) | YES | | | | | content | text | YES | | | | +-----------------+----------------+----------------+---------------+-------------------+-----------------+ mysql>show variables like "innodb_file_format"; +-------------------------+-----------------+ | Variable_name | Value | +-------------------------+-----------------+ | innodb_file_format | Barracuda | +-------------------------+-----------------+ mysql>show table status like "db_page" \G *************************** 1. row *************************** Name: db_page Engine: InnoDB Version: 10 Row_format: Compact Rows: 2 Avg_row_length: 8192 Data_length: 16384 Max_data_length: 0 Index_length: 0 Data_free: 0 Auto_increment: 3 Create_time: 2017-03-07 13:30:19 Update_time: Check_time: Collation: utf8_general_ci Checksum: Create_options: Comment: Block_format: Original
在 msyql 5.7.9 及之後版本,默認行格式由innodb_default_row_format
變量決定,它的默認值是DYNAMIC
,也能夠在 create table 的時候指定ROW_FORMAT=DYNAMIC
。
注意,若是要修改現有表的行模式爲compressed
或dynamic
,必須先將文件格式設置成Barracuda:set global innodb_file_format=Barracuda;
,再用ALTER TABLE tablename ROW_FORMAT=COMPRESSED;
去修改才能生效,不然修改無效卻無提示
變長大字段類型包括blob,text,varchar,其中varchar列值長度大於某數N時也會存溢出頁,在latin1字符集下N值能夠這樣計算:innodb的塊大小默認爲16kb,因爲innodb存儲引擎表爲索引組織表,樹底層的葉子節點爲一雙向鏈表,所以每一個頁中至少應該有兩行記錄,這就決定了innodb在存儲一行數據的時候不可以超過8k,減去其它列值所佔字節數,約等於N。對於InnoDB,內存是極爲珍貴的,若是把768字節長度的blob都放在數據頁,雖然能夠節省部分IO,可是能緩存行數就變少,也就是能緩存的索引值變少了,下降了索引效率
dynamic行格式,列存儲是否放到off-page頁,主要取決於行大小,它會把行中最長的那一列放到off-page,直到數據頁能存放下兩行。TEXT/BLOB列 <=40 bytes 時老是存放於數據頁。這種方式能夠避免compact那樣把太多的大列值放到 B-tree Node,由於dynamic格式認爲,只要大列值有部分數據放在off-page,那把整個值放入都放入off-page更有效。
compressed 物理結構上與dynamic相似,可是對錶的數據行使用zlib算法進行了壓縮存儲。在long blob列類型比較多的狀況下用,能夠下降off-page的使用,減小存儲空間(通常40%左右),但要求更高的CPU,buffer pool裏面可能會同時存儲數據的壓縮版和非壓縮版,因此也多佔用部份內存。這裏 MySQL 5.6 Manual innodb-compression-internals 講的十分清楚。
另外,因爲ROW_FORMAT=DYNAMIC
和 ROW_FORMAT=COMPRESSED
是從 ROW_FORMAT=COMPACT
變化來的,因此他們處理 CHAR
類型存儲的方式和 COMPACT 同樣。
mysql的 io 以page爲單位,所以沒必要要的數據(大字段)也會隨着須要操做的數據一同被讀取到內存中來,這樣帶來的問題因爲大字段會佔用較大的內存(相比其餘小字段),使得內存利用率較差,形成更多的隨機讀取。從上面的分析來看,咱們已經看到性能的瓶頸在於因爲大字段存放在數據頁中,形成了內存利用較差,帶來過多的隨機讀,那怎麼來優化掉這個大字段的影響
a、innodb提供了barracuda文件格式,將大字段徹底存放在溢出段中,數據段中只存放20個字節,這樣就大大的減少了數據頁的空間佔用,使得一個數據頁可以存放更多的數據行,也就提升了內存的命中率(對於本實例,大多數行的長度並無超過8k,因此優化的幅度有限);若是對溢出段的數據進行壓縮,那麼在空間使用上也會大大的下降,具體的的壓縮比率能夠設置key_blok_size來實現。
b、能夠把大字段用COMPRESS()壓縮後再存爲BLOB,或者在發送到MySQL前在應用程序中進行壓縮
c、一張表有多個類blob字段,把它們組合起來如<TEXT><f_big_col1>long..</f_big_col1> <f_content>long..</f_content></TEXT>
,再壓縮存儲
d、若是預期長度範圍varchar就知足,就避免使用TEXT
將主表拆分爲一對一的兩個關聯表,將大字段單獨放到另一張表後,單行長度變的很是的小,page的行密度相比原來的表大不少,這樣就可以緩存足夠多的行,buffer pool的命中率就會提升,應用程序須要額外維護的是一張大字段的子表,還能夠經過覆蓋索引來優化,將索引和原表結構分開,從訪問密度較小的數據頁改成訪問密度很大的索引頁,隨機io轉換爲順序io
總結:仍是讓單個page可以存放足夠多的行,不斷的提示內存的命中率,從數據庫底層存儲的原理出發,可以更深入的優化數據庫
綜上,若是在實際業務中,確實須要在InnoDB表中存儲BLOB、TEXT、長VARCHAR列時,有下面幾點建議:
儘量將全部數據序列化、壓縮以後,存儲在同一個列裏,避免發生屢次off-page
若是預期長度範圍varchar就知足,就避免使用TEXT
若是沒法將全部列整合到一個列,能夠退而求其次,根據每一個列最大長度進行排列組合後拆分紅多個子表,儘可能是的每一個子表的總行長度小於8KB,減小發生off-page的頻率
http://www.hudong.com/wiki/%E3%80%8AMySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%9AInnoDB%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E%E3%80%8B
http://www.mysqlperformanceblog.com/2008/01/11/mysql-blob-compression-performance-benefits/
http://www.mysqlperformanceblog.com/2012/05/30/data-compression-in-innodb-for-text-and-blob-fields/
http://yoshinorimatsunobu.blogspot.com/2010/11/handling-long-textsblobs-in-innodb-1-to.html
http://blog.opskumu.com/mysql-blob.html
http://hidba.org/?p=551
http://blog.chinaunix.net/uid-24485075-id-3523032.html
http://dev.mysql.com/doc/refman/5.6/en/innodb-row-format-dynamic.html