mysql變長類型字段varchar值更新變長或變短底層文件存儲原理


  為了搞清楚MySQL對於可變長度字段值修改時,如何高效操作數據文件的機制。之前一直模糊不清,網上也搜不到現成的答案。經過多方資料搜集整理。寫出此文供大家一起參閱。由於涉及眾多非常底層的知識,我假設讀者已經對操作系統和磁盤存取有一定的基礎知識。文中如有疏漏,還請大佬指正。
  為了探究這個問題,我們要先來回顧一下我之前的一篇文章《文件隨機或順序讀寫原理深入淺出》講的文件存儲的底層原理知識。如下圖所示。一個文件的數據是以塊為單位存儲到物理磁盤的隨機位置,這是由操作系統負責管理的,用戶程序無權決定。所以在文件視圖層面我們連續存儲的數據,映射到物理磁盤層面就是隨機位置了。圖中是假設磁盤塊大小為32KB,則文件對應的數據偏移地址存儲到對應的物理塊中示意圖。
  我們現在假設MySQL的一張表對應一個數據文件。那么表里的數據按行存儲,則行與行之間的數據被緊密的連續填充到上圖“數據文件視圖”中,並被以塊為單位隨機存儲到了物理磁盤上。這樣遍歷表時,就可以從文件0地址開始依次讀取到所有數據,行與行之間的間隔符就是普通的換行符。每一行中的字段都按照表結構定義中的字段長度讀取即可。這里先假設表中的字段都是定長類型的。這樣就不會有什么問題。即便是更新某個字段的值,則可以直接使用隨機文件讀取方法定位到字段的偏移地址寫入新值即可覆蓋舊值。
  那么現在問題來了,如果表中某字段是可變長類型的如varchar(50)。數據插入時假設值為“月光冷鋒”,后面有個更新操作,需要將值改為“月光冷鋒的博客”。此時會發生什么呢?由於是可變長度的字段類型,文件中該行的該字段實際占用空間就是四個字符,而不是50個字符。且行之間數據緊密排列存儲。現在值要變成7個字符,則我們無法通過簡單的隨機文件存取定位到該字段覆蓋舊值,這樣會出現嚴重的問題,會覆蓋其他字段或行的值。一種古老的文件修改方法是,先將要修改的位置后面的數據都轉移到一個臨時文件中,然后修改現在的位置處的值,最后再把臨時文件的內容拼接回原文件,從而實現修改操作。顯然這種方式無法應用到頻繁更新的數據庫上。這種方式代價巨大,不過大多數文件都是只讀的,極少更新。所以應用也挺廣泛的。比如音頻、視頻文件。
  那么MySQL是如何解決這類問題的呢?首先我們如果把表數據文件看成一個可以任意發揮的平台,我們不在像其他類型文件那樣直接將數據一個挨着一個緊密的寫入文件的偏移地址中。MySQL使用頁這個概念重新對文件視圖划分,也就是一個頁對應一段文件的偏移地址。如下圖所示。MySQL頁大小默認16kB,剛好對應圖中兩個頁對應到一個物理塊32kb大小。MySQL的頁使用雙向鏈表方式組織。

 

  下面我們來看下如果可變長度varchar類型的字段值變長了,文件里的數據該怎么存儲呢?因為表中數據剛開始插入時,可變長度字段值都是根據實際長度存儲下來的,且行與行之間數據也是緊密連續存放在文件地址中的(再次強調一下,不是連續存放在物理磁盤上的)。那么現在值變長了,原來的位置無法擴展出新的空間出來,所以無法覆蓋存放到原來的位置上。此時MySQL就會使用頁分裂的方法擴展字段變長的空間。
  比如假設行10的數據存放在頁③的位置,如下圖2所示。行11也是存放在頁③的位置,且他們兩行都把頁③填滿了。現在更新行10的某個變長字段值,由“月光冷鋒”改成“月光冷鋒的博客”。MySQL將新創建一個頁⑥出來(相應的原數據文件也要變大增長),把原來頁③的內容,行10和行11的數據重新生成排列一遍存儲到頁③和頁⑥中,同時將原來的頁鏈表結構重新修改其前驅和后繼頁節點的指針就OK了。如下圖3所示。
  圖2          圖3                                                                           
  MySQL就是通過這種技巧,實現了修改數據文件時,不必像傳統修改文件那樣付出昂貴代價。這種方式雖然解決了修改文件時避免大規模移動數據的弊端,但是讀取這些數據時,卻無法像傳統存取方式那樣,直接從文件偏移地址0開始順序讀取。而是要根據頁的鏈表結構順序讀取。需要不斷的計算和移動文件偏移量指針,好在這個過程不會花費多少代價。但是會帶來另外一個比較嚴重的問題就是頁空洞,也稱為碎片。上面行11有部分字段數據已經轉移到了頁⑥中,顯然頁⑥是沒有存滿的。行12是存在頁④中的,這樣就產生了碎片問題,浪費了文件的一些地址空間,這些空洞存的都是特殊占位符,也要占據真實的物理磁盤空間。隨着更新刪除操作越來越多,碎片也會越來越多,所以有必要定期進行表的碎片整理,這樣可以收縮表文件占據的磁盤空間。也可以降低頁鏈表的長度,從而節省一些尋址操作代價。
  如果可變長字段值由大變小,則原來的字段值地址空間足夠了,也就不需要新加頁了,只需要重新整理排列一下當前更新的行數據即可。使得變成字段的值占用實際空間即可。至於留下的頁碎片問題,MySQL也有相應的機制做合並優化操作。我這里不做深究。
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM