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

来源:https://www.cnblogs.com/qcloud1001/archive/2018/12/27/10184160.html
-Advertisement-
Play Games

本文由雲+社區發表 一、從常見的報錯說起 ​ 故事的開頭我們先來看一個常見的sql報錯信息: ​ 相信對於這類報錯大家一定遇到過很多次了,特別對於OMG這種已內容生產為主要工作核心的BG,在內容線的存儲中,數據大一定是個繞不開的話題。這裡的數據“大”,遠不止存儲空間占用多,其中也包括了單個(表)欄位 ...


本文由雲+社區發表

一、從常見的報錯說起

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

img

​ 相信對於這類報錯大家一定遇到過很多次了,特別對於OMG這種已內容生產為主要工作核心的BG,在內容線的存儲中,數據大一定是個繞不開的話題。這裡的數據“大”,遠不止存儲空間占用多,其中也包括了單個(表)欄位存儲多、大,數據留存時間長,數據冗餘多,冷熱數據不明顯導致的體量大,訪問峰值隨著熱點變化明顯,邏輯處理複雜導致數據存儲壓力放大等等。回到這個報錯的問題上來,我們先來看一下這個表的結構:

img

看到這裡,我相信大家會有不同的處理方式了,這裡就不對各種處理方式的優劣做比較了,僅僅敘述使用頻率較高的兩種處理方式。

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

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

二、深入探索

​ 接著我們再來深入分析下關於限制大小“65535”的一些容易混淆的概念。

1、“65535”不是單個varchar(N)中N的最大限制,而是整個表非大欄位類型的欄位的bytes總合。

---------------------------------------------------------------------------------------------

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.

---------------------------------------------------------------------------------------------

2、不同的字元集對欄位可存儲的max會有影響,例如,UTF8字元需要3個位元組存儲,對於VARCHAR(255)CHARACTER SET UTF8列,會占用255×3 =765的位元組。故該表不能包含超過65,535/765=85這樣的列。GBK是雙位元組的以此類推。

3、可變長度列在評估欄位大小時還要考慮存儲列實際長度的位元組數。例如,VARCHAR(255)CHARACTER SET UTF8列需要額外的兩個位元組來存儲值長度信息,所以該列需要多達767個位元組存儲,其實最大可以存儲65533位元組,剩餘兩個位元組存儲長度信息。

4、BLOB、TEXT、JSON列不同於varchar、char等欄位,列長度信息獨立於行長存儲,可以達到65535位元組真實存儲

5、定義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,該標記存儲在動態行首,動態表具體可以根據

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

7、InnoDB允許單表最多1000個列

8、varchar主鍵只支持不超過767個位元組或者768/2=384個雙位元組 或者767/3=255個三位元組的欄位 而GBK是雙位元組的,UTF8是三位元組的

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

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

三、真正的故障

​ 下麵來說下今天遇到的業務故障,線上業出現了大量的如下報錯,導致程式無法寫入數據:

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的演算法進行壓縮。

相關的變更操作就相對簡單了:

1、 修改MySQL全局變數:

SET GLOBAL innodb_file_format='Barracuda';

2、平滑變更原表的屬性:

ROW_FORMAT=COMPRESSED

四、繼續學習

​ 通過這個案例我們可以從中提煉出兩個值得深入研究一下的點:

1、關於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只能讀取到小部分。

2、關於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前在應用程式中進行壓縮,可以獲得顯著的空間優勢和性能收益。

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

此文已由作者授權騰訊雲+社區發佈



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 電腦由於裝了不相容的.net framework組件導致上不了網,本地DHCP服務無法開啟報錯誤代碼1075的解決辦法。 ...
  • 1.MySQL埠 因為上一篇文章我就已經給MySQL新建了一新用戶,且賦予了遠程連接資料庫的所有許可權(GRANT ALL PRIVILEGES ON *.* TO 'newuser' @ '%' IDENTIFIED BY 'newpass' WITH GRANT OPTION;) 且錯誤是 Ca ...
  • 使用navicat或者其他資料庫管理工具連接mysql時出現2003-Can’t connect to MySQL server (10060)錯誤 可能造成出現的原因: 1.網路不通暢 2.mysql 服務未啟動 3.防火牆未開放埠 解決方法: 1.首先確定網路和服務問題,用ssh工具登陸資料庫 ...
  • 1,PowerDesigner Code和Name設置大寫 tool》MODEL OPTIONS 2.從oracle資料庫導出的表結構預設包含了tablespace 刪除tablespace方法,1:設置用戶名為none; 2: 如果表空間tablespace 實在去除不掉 如 create tab ...
  • 一 .Server Trigger的簡單介紹 在SQL Server資料庫中,Server Trigger 是一種特殊類型的存儲過程,它可以對特定表、視圖或存儲中的必然事件自動響應,不由用戶調用。創建觸發器時對其進行定義,以便在對特定的資料庫對象作特定類型的修改時執行,根據觸發器定義的動作做出反應。 ...
  • 一. ASK錯誤 集群上篇最後講到,對於重新分片由redis-trib負責執行,關於該工具以後再介紹。在進行重新分片期間,源節點向目標節點遷移一個槽的過程中,可以會出現該槽中的一部分鍵值對保存在源節點中,另一部份鍵值對則保存在目標節點中。 當客戶端向源節點發送一個與資料庫鍵有關的命令時,並且命令要處 ...
  • 項目開發中經常會遇到用戶在評論或者發表文章的時候會打一些表情在裡面,如果我們在開發中不去做一些處理的話,表情會出不來的,甚至是報錯,下麵簡單介紹處理方式. 項目中評論或者文章中寫入表情符號時,php報錯,或者出來時一些????。這是為什麼呢? 下麵說一下mysql中的字元集: mysql資料庫... ...
  • 問題:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)問題描述:在linux中使用mysql -uroot -p 輸入密碼提示如上錯誤解決方法: 1、先停掉原來的服務 service ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...