【MySQL經典案例分析】關於數據行溢出由淺至深的探討

本文由雲+社區發表

1、從常見的報錯提及

​ 故事的開頭咱們先來看一個常見的sql報錯信息:mysql

img

​ 相信對於這類報錯你們必定遇到過不少次了,特別對於OMG這種已內容生產爲主要工做核心的BG,在內容線的存儲中,數據大必定是個繞不開的話題。這裏的數據「大」,遠不止存儲空間佔用多,其中也包括了單個(表)字段存儲多、大,數據留存時間長,數據冗餘多,冷熱數據不明顯致使的體量大,訪問峯值隨着熱點變化明顯,邏輯處理複雜致使數據存儲壓力放大等等。回到這個報錯的問題上來,咱們先來看一下這個表的結構:算法

img

看到這裏,我相信你們會有不一樣的處理方式了,這裏就不對各類處理方式的優劣作比較了,僅僅敘述使用頻率較高的兩種處理方式。sql

  • 根據報錯的指引,把兩個大的varchar(22288)改爲text、blob
  • 根據業務特色,縮小varchar的存儲長度,或者按照規則拆分紅多個小的vachar和char

​ 這兩種的處理方式也各有優缺點,把字段改爲text或者blob,不只增大了數據存儲的容量,對這個字段的索引頁只能採用前綴或者全文索引了,若是業務側存儲的是json格式的數據,5.7支持json數據類型是個不錯的選擇,能夠針對單個子類進行查詢和輸出。一樣若是縮小和拆分的話就比較依賴業務的場景和邏輯需求了,業務使用的邏輯上須要修改,工程量也須要評估。數據庫

2、深刻探索

​ 接着咱們再來深刻分析下關於限制大小「65535」的一些容易混淆的概念。json

一、「65535」不是單個varchar(N)中N的最大限制,而是整個表非大字段類型的字段的bytes總合。app

---------------------------------------------------------------------------------------------less

Every table (regardless of storage engine) has a maximum row size of 65,535 bytes. Storage engines may place additional constraints on this limit, reducing the effective maximum row size.性能

---------------------------------------------------------------------------------------------學習

二、不一樣的字符集對字段可存儲的max會有影響,例如,UTF8字符須要3個字節存儲,對於VARCHAR(255)CHARACTER SET UTF8列,會佔用255×3 =765的字節。故該表不能包含超過65,535/765=85這樣的列。GBK是雙字節的以此類推。測試

三、可變長度列在評估字段大小時還要考慮存儲列實際長度的字節數。例如,VARCHAR(255)CHARACTER SET UTF8列須要額外的兩個字節來存儲值長度信息,因此該列須要多達767個字節存儲,其實最大能夠存儲65533字節,剩餘兩個字節存儲長度信息。

四、BLOB、TEXT、JSON列不一樣於varchar、char等字段,列長度信息獨立於行長存儲,能夠達到65535字節真實存儲

五、定義NULL列會下降容許的最大列數。

  • InnoDB表,NULL和NOT NULL列存儲大小是同樣
  • MyISAM表,NULL列須要額外的空間記錄其值是否爲NULL。每一個NULL須要一個額外的位(四捨五入到最接近的字節)。最大行長度計算以下:

​ row length = 1 + (sum of column lengths) + (number of NULL columns + delete_flag + 7)/8 + (number of variable-length columns)

  • ​ 靜態表,delete_flag = 1,靜態表經過在該行記錄一個位來標識該行是否已被刪除。
  • ​ 動態表,delete_flag = 0,該標記存儲在動態行首,動態表具體能夠根據

六、對於InnoDB表,NULL和NOT NULL列存儲大小是同樣

七、InnoDB容許單表最多1000個列

八、varchar主鍵只支持不超過767個字節或者768/2=384個雙字節 或者767/3=255個三字節的字段 而GBK是雙字節的,UTF8是三字節的

九、不用的引擎對索引的限制有區別

  • innodb每一個列的長度不能大於767 bytes;全部組成索引列的長度和不能大於3072 bytes
  • myisam 每一個列的長度不能大於1000 bytes,全部組成索引列的長度和不能大於1000 bytes

3、真正的故障

​ 下面來講下今天遇到的業務故障,線上業出現了大量的以下報錯,致使程序沒法寫入數據:

img

按照提示和正常的思路,咱們先第一反應認爲業務存在以下的問題:

  • 設置的表結構中字段超過了限制
  • 某個字段插入的數據長度超過了改字段設置的max值

​ 接着查看了業務的庫表結構,以下:

img

​ 很快排除了第一個緣由,由於首先業務的報錯不是在創建表的時候出現的,若是是表中非大字段之和65535,在建表的時候就會出錯,而業務是在寫入的時候才報錯的,並且經過庫表結構也能發現大量的都是mediumblob類型字段,非大字段加起來遠小於65535。

​ 接着根據業務提供的具體SQL,appversion、datadata、elt_stamp、id這幾個非大字段,也並無超過限制,mediumblob類型字段最大可存儲16M,業務的數據遠遠沒有達到這個量級。按照報錯的提示把 appversion、datadata、elt_stamp、id這幾個非大字段均改爲blob類型,仍是沒法解決。(根據以前的分析,必然不是問題的根源)。

​ 冷靜下來後,發現其實還有個細節被忽略掉了,業務的失敗率不是100%,說明仍是有成功的請求,經過對比成功和失敗的sql,發現果真數據量差別的仍是mediumblob類型字段。那麼如今第一個想到的就是,max_allowed_packet這個參數,是否是調小了,是的單個請求超過大小被拒絕了,查了下配置的值(以下圖),配置的大小1G,sql的數據長度遠沒有這麼大,這個緣由也排除了。

img

​ 查到這裏基本上排除了常見幾個問題,接着再看一下另外一個參數的限制:innodb_page_size,這個的默認值是16K,每一個page兩行數據,因此每行最大8k數據。

查看了下數據表Row_format是Compact,那麼咱們能夠推斷問題的緣由應該就是innodb默認的approach存儲格式會把每一個blob字段的前864個字節存儲在page裏,因此blob超過必定數量的話,單行大小就會超過8k,因此就報錯了。經過對比業務寫成功和失敗的SQL也應徵了這個推論,那麼如今要怎麼解決這個問題?

  • 業務拆分表,大字段進行分表存儲
  • 經過解決Row_format的存儲方式解決問題

    因爲業務單表的存儲條數並不大,並且業務邏輯不適合拆分,因此咱們要在Row_format上來解決這個問題。

​ Barracuda文件格式下擁有兩種新的行記錄格式Compressed和Dynamic兩種,新的兩種格式對於存放BLOB的數據採用了徹底的行溢出的方式,在數據頁中只存放20個字節的指針,實際的數據都存放在BLOB Page中。Compressed行記錄格式的另外一個功能就是存儲在其中的數據會以zlib的算法進行壓縮。

相關的變動操做就相對簡單了:

一、 修改MySQL全局變量:

SET GLOBAL innodb_file_format='Barracuda';

二、平滑變動原表的屬性:

ROW_FORMAT=COMPRESSED

4、繼續學習

​ 經過這個案例咱們能夠從中提煉出兩個值得深刻研究一下的點:

一、關於innodb_page_size

​ 從MySQL5.6開始,innodb_page_size能夠設置Innodb數據頁爲8K,4K,默認爲16K。這個參數在一開始初始化時就要加入my.cnf裏,若是已經建立了表,再修改,啓動MySQL會報錯。

那麼在5.6的版本以前要修改這個值,怎麼辦?那隻能是在源碼上作點文章了,而後從新rebuild一下MySQL。

img

​ UNIV_PAGE_SIZE是數據頁大小,默認的是16K,該值是能夠設置必須爲2的次方。對於該值能夠設置成4k、8k、16k、32K、64K。同時更改了UNIV_PAGE_SIZE後須要更改UNIV_PAGE_SIZE_SHIFT 該值是2的多少次方爲UNIV_PAGE_SIZE,因此設置數據頁分別狀況以下:

img

​ 接着再來講一下innodb_page_size設置成不一樣值的對於mysql性能上的影響,測試的表含有1億條記錄,文件大小30G。

​ ①讀寫場景(50%讀50%寫)

​ 16K,對CPU壓力較小,平均在20%

​ 8K,CPU壓力爲30%~40%,但select吞吐量要高於16K

​ ②讀場景(100%讀)

​ 16K和8K差異不明顯

InnoDB Buffer Pool管理頁面自己也有代價,Page數越多,那麼相同大小下,管理鏈表就越長。所以當咱們的數據行自己就比較長(大塊插入),更大的頁面更有利於提高速度,由於一個頁面能夠放入更多的行,每一個IO寫的大小更大,能夠更少的IOPS寫更多的數據。 當行長超過8K的時候,若是是16K的頁面,就會強制轉換一些字符串類型爲TEXT,把字符串主體轉移到擴展頁中,會致使讀取列須要多一個IO,更大的頁面也就支持了更大的行長,64K頁面能夠支持近似32K的行長而不用使用擴展頁。 可是若是是短小行長的隨機讀取和寫入,則不適合使用這麼大的頁面,這會致使IO效率降低,大IO只能讀取到小部分。

二、關於Row_format

​ Innodb存儲引擎保存記錄,是以行的形式存放的。在InnoDB 1.0.x版本以前,InnoDB 存儲引擎提供了 Compact 和 Redundant 兩種格式來存放行記錄數據。MySQL 5.1 中的innodb_plugin 引入了新的文件格式:Barracuda,該文件格式擁有新的兩種行格式:compressed和dynamic。而且把 compact 和 redundant 合稱爲Antelope。能夠經過命令SHOW TABLE STATUS LIKE 'table_name';來查看當前表使用的行格式,其中 row_format 列表示當前所使用的行記錄結構類型。

​ MySQL 5.6 版本中,默認 Compact ,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;去修改才能生效,不然修改無效卻無提示。

①compact

若是blob列值長度 <= 768 bytes,不會發生行溢出(page overflow),內容都在數據頁(B-tree Node);若是列值長度 > 768字節,那麼前768字節依然在數據頁,而剩餘的則放在溢出頁(off-page),以下圖:

img

​ 上面講的blob或變長大字段類型包括blob、text、varchar,其中varchar列值長度大於某數N時也會存溢出頁,在latin1字符集下N值能夠這樣計算:innodb的塊大小默認爲16kb,因爲innodb存儲引擎表爲索引組織表,樹底層的葉子節點爲一雙向鏈表,所以每一個頁中至少應該有兩行記錄,這就決定了innodb在存儲一行數據的時候不可以超過8k,減去其它列值所佔字節數,約等於N。

②compressed或dynamic

對blob採用徹底行溢出,即彙集索引記錄(數據頁)只保留20字節的指針,指向真實存放它的溢出段地址:

img

​ 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的使用,減小存儲空間(50%左右,可參見以前「【數據庫評測報告】第三期:innodb、tokudb壓縮性能」報告中的測試結果),但要求更高的CPU,buffer pool裏面可能會同時存儲數據的壓縮版和非壓縮版,因此也多佔用部份內存。

​ 最後參考了《高性能MySQL》,給出一些使用BLOB這類變長大字段類型的建議:

​ ①大字段在InnoDB裏可能浪費大量空間。例如,若存儲字段值只是比行的要求多了一個字節,也會使用整個頁面來存儲剩下的字節,浪費了頁面的大部分空間。一樣的,若是有一個值只是稍微超過了32個頁的大小,實際上就須要使用96個頁面。

​ ②太長的值可能使得在查詢中做爲WHERE條件不能使用索引,於是執行很慢。在應用WHERE條件以前,MySQL須要把全部的列讀出來,因此可能致使MySQL要求InnoDB讀取不少擴展存儲,而後檢查WHERE條件,丟棄全部不須要的數據。

​ ③一張表裏有不少大字段,最好組合起來單獨存到一個列裏面。讓全部的大字段共享一個擴展存儲空間,比每一個字段用本身的頁要好。

​ ④把大字段用COMPRESS()壓縮後再存爲BLOB,或者在發送到MySQL前在應用程序中進行壓縮,能夠得到顯著的空間優點和性能收益。

​ ⑤擴展存儲禁用了自適應哈希,由於須要完整的比較列的整個長度,才能發現是否是正確的數據。

此文已由做者受權騰訊雲+社區發佈

相關文章
相關標籤/搜索