MySQL講一講
目錄
前言
資料庫是我們平時接觸最多的中介軟體,大多數同學對資料庫的理解,只停留在了CURD操作。但是在高併發以及服務高可用需求的情況下,資料庫往往是整個系統服務的瓶頸所在,也是重構中最難優化的部分。
所以,在專案初期設計表結構時,應儘量遵循設計規範,避免後期優化時,對系統帶來嚴重影響。
但是大家有沒有想過,我們遵循的設計規範到底是否合理,設計規範又是怎麼形成的?這就是我們本篇wiki的要解決的問題,去看看資料庫設計實現原理。
概述
資料庫種類很多,有關係型資料庫,非關係型資料庫,有記憶體型資料庫,也有持久化型資料庫。本篇wiki以關係型資料庫,MySQL為例講解資料庫實現原理,暫不涉及其他資料庫。
由於本人能力有限,對於MySQL也只能算是一知半解,接下來的講述中如果有講述不清楚,或者不對的地方還望不吝賜教。
MySQL是一個龐大的系統,本文不可能涵蓋所有,本文按照MySQL整體邏輯架構、儲存管理以及InnoDB儲存引擎的簡單介紹,三個主要系統來講述。
1.mysql邏輯架構
1.1總體架構
- 最上層的服務並不是MySQL所獨有的大多數基於網路的伺服器程式都有類似的結構如:連線處理、授權認證、安全等
- 大多數MySQL的核心服務功能都在第二層如:查詢解析、分析、優化、快取以及所有的內建函式跨儲存引擎的功能都在這一層:儲存過程、觸發器、檢視等
- 最底層,儲存引擎負責資料的儲存和提取,每種儲存引擎都有其優勢和劣勢上層通過API與儲存引擎互動,API遮蔽了不同儲存引擎間實現差異不同儲存引擎之間不會相互通訊,(就像模範,我們設計模範,由資料提供方具體實現)只是單純響應上層的請求引擎是基於表的,而不是基於資料庫的。很多大公司,也是在使用innoDB,只不過是對innoDB做了修改。
1.2儲存引擎型別
MySQL5.0之後提供了以下幾種儲存引擎
點選此處展開...
對於不同儲存引擎我們需要考慮一下幾個方面
點選此處展開...
1.3儲存引擎之間的區別
- MyISAM
MyISAM儲存引擎不支援事務。其優勢是訪問速度快。所以如果一個表對事務沒有要求,並且以Select為主。那麼使用MyISAM儲存引擎是比較合適的。例如系統的配置資訊。黑名單、情報資訊(惡意IP)等等。每張MyISAM表在磁碟上儲存三個檔案
1. frm檔案(表的定義檔案,表的結構)
2. MYD檔案(表的資料儲存檔案)
3. MYI檔案(表的索引儲存檔案)
- InnoDB
現在MySQL預設都是使用InnoDB儲存引擎。他提供了很多非常有用的功能例如:行鎖、事物、外來鍵約束、自增ID。但是相比MyISAM儲存引擎他會佔用更多的磁碟來保留資料和索引。
InnoDB的資料和索引的兩種儲存方式,先做一個簡單介紹,後面還會詳細講解:
•使用共享表空間儲存。這種儲存方式中表結構儲存在.frm檔案中。資料檔案儲存在innodb_data_home_dir中。索引檔案儲存在innodb_data_file_path中。
•使用多表儲存空間。這種儲存方式表結構儲存在.frm檔案中。表的資料和索引一起儲存在.idb檔案中。多表儲存空間的檔案沒有大小限制。現在一般都配置使用多表空間儲存。 - Memory
Memory儲存引擎的表資料檔案儲存於記憶體之中。但是表結構空間.frm檔案儲存於磁碟。他的優點是記憶體訪問速度非常快,並且預設使用hash索引。缺點是一旦資料庫關閉。表中的資料就會丟失。每個Memory型別的表都可以設定表資料量的大小。可以使用max_heap_table_size來配置。預設是16M。
2.輔助儲存管理(重點)
這是我們需要重點講解的一部分。
輔助儲存其實是從英語翻譯過來的,原文是Secondary Storage,翻譯成輔助儲存還是比較合適的,其實我們實際看這個儲存,就可以簡單的當成磁碟。
2.1儲存器層次
我們看一下儲存器的層次,資料庫的主要功能肯定就是儲存資料,那麼這些資料到底如何儲存,他會涉及不同層次的儲存器,那麼我們從下面往上來看。
1.最底下一層,cache層,也就是快取記憶體,這一層包括CPU的暫存器,CPU共享的以及獨享的快取記憶體,也就是L1、L2、L3,資料和指令從記憶體中移到快取記憶體中這個時間非常短,只要幾納秒。
2.主儲存器,也就是Main Memory,翻譯成主儲存器也算是直譯,其中我們中文更常說的就是記憶體,後面的解說中主儲存器也就等同於記憶體。它是計算機的活動中心,磁碟上的資料都必須讀取到記憶體中,然後再從記憶體讀取到快取記憶體中讀,將資料從記憶體移到快取記憶體或者處理器的速度大概在10-100納秒。這個速度也是非常快的,快到我們幾乎不用考慮。
3.輔助儲存層,典型的輔助儲存器就是磁碟,也就是我們俗稱的硬碟。磁碟到主存間傳送一個位元組的時間在10毫秒,這裡需要注意是傳送一個位元組的時間,但我們平時往往是大段資料進行讀取,那麼這個時間開銷就比較大了。接下來我們會詳細講解。這裡稍微聊一下虛擬記憶體,大家在學習作業系統這門課的時候應該有了解,虛擬記憶體其實還是記憶體,它是為了解決實體記憶體不夠大的情況的解決方案,這裡不再展開討論,感興趣的同學可以看一下作業系統這本書。
4.第三級儲存器,比如DVD,磁帶等,我們不太常用,這裡就不再詳解了。開個腦洞,看過三體的同學可能會注意到一個細節,地球在被毀滅之前曾經試圖將人類文明進行儲存,要求儲存時間至少億年級別,作者最後給出的結果是,只有將故事刻在石頭上。
2.2儲存器層次間傳輸資料
正常情況下,資料都是在相鄰的層次間進行傳遞,以從磁碟到記憶體間傳遞資料為例,由於訪問想要的資料和查詢指定的位置以及寫入資料會消耗大量的時間,所以,當需要資料時,我們往往不是一個位元組一個位元組的讀和寫,而是把它打包起來,一塊一塊的去讀和寫。
在輔助儲存中,磁碟就被劃分為磁碟塊,每個塊的大小是4~64KB,整個資料塊在主存和磁碟間移進移出,這裡需要注意一下,比如某次只需1K的資料,而對應的這個塊是64K,本次查詢還是讀取整個塊的資料。那麼在設計資料庫時,最好的情況是什麼呢,就是當讀取這1KB的時候,是不是很有可能把其他的有用資料都同時讀取出來,這樣的話,我們讀取一塊的資料,有可能就滿足了兩次甚至三次查詢要求,所以我們在設計表結構時,可以將那些一個位元組一個位元組的欄位放在一起,這樣的話,就可以減少讀取磁碟的次數。
當然這裡講解的是設計DBMS,會比DBA更深入一層。其實這裡講的檔案系統以及虛存,都是作業系統級別的概念,而資料庫對於磁碟的管理往往繞過作業系統,同時也就繞開了作業系統的限制,它直接對磁碟的裸裝置進行管理,它自己實現了一套檔案系統,我們在學習作業系統時,也是有塊的概念,但是這裡的塊和作業系統的塊在概念上不完全一樣,但是其設計思想和理念是一至的,就是為了不一個位元組一個位元組讀取資料,那麼為什麼要這麼設計,後面我們會用數學方法來說明。
在記憶體和快取記憶體之間傳輸,是以快取記憶體線為基本單位的,一般是32個連續位元組。這裡我們可以看到,即使讀取速度是幾十納秒,都不會一個位元組一個位元組的讀取,所以我們也希望整個快取記憶體線也能夠被一起使用。
2.3易失和非易失儲存器
接下來我們聊一下儲存器的易失性,這裡的易失性並不是我們上面講的,要儲存幾億年,這是在時間上的易失性。而我們要講的是在電力維度上的易失性。上面的兩層都是在斷電後可以不丟失資料的儲存裝置,下面的兩層都是斷電之後就會丟失的。
這裡需要說明的一個點是,我們都說DBMS非常複雜,為什麼會複雜呢,如果只是一個簡單的檔案系統的話,也就是上面講的那點東西。之所以複雜,就是因為在資料庫中的任何修改都不能認為是最終有效的,直到該修改被儲存到非易失裝置上。也就是,如果我們修改一個數據,在記憶體中是非常容易的,但是記憶體中修改完成之後還要同步到磁碟上,那麼如何平衡易失和非易失上的效能差異,這是主要矛盾。當然這次分享的內容,沒有涉及到事物,如果後續還有機會,我會給大家詳細講解事物,就會考慮到事物檔案,事物檔案也不是一直往磁碟上寫,也是先寫到記憶體中,再選擇合適的時機同步到磁碟上,那麼理論基礎就在於這裡了。
2.4磁碟結構
接下來就聊一下大家非常感興趣的地方,從硬碟讀取一個位元組的時間是10毫秒的問題。
首先先看一下磁碟的結構,這裡講解的是機械硬碟的結構。
磁碟驅動器主要包括:磁碟組合和磁頭組合。
磁碟組合就是圖中圓片,類似於光碟一樣,他們都是圍繞中間的軸做高速旋轉。
旁邊的機械臂上就是磁頭,磁頭是懸浮在磁碟表面上的,來讀取磁碟上磁資訊,有磁資訊是1,無磁資訊是0。這就是所謂的二進位制的儲存。磁碟的上下兩面都可以儲存。
通過下面的俯檢視,我們可以看到,這是一圈圈的同心圓,這些同心圓我們定義為磁軌。也就是說,磁頭就是在磁軌上高速旋轉劃過一圈。
因為光碟是疊加在一起的,相同半徑的磁軌就組成了柱面。
磁軌又會被組織成扇區,扇區是被間隙分割的圓的片段,間隙未被磁化成0或者1.間隙約佔整個磁軌的10%,間隙可以幫助定位扇區的起點。
就讀寫磁碟來說,扇區是不可分割的單位,這是由硬體決定的,每次讀都是讀整個扇區的資料,不可能只讀一部分。
就磁碟錯誤來說,扇區也是不可分割的單位,也就是說,如果某個扇區中的一個數據位出錯,那麼整個扇區也都不能再用。
這裡補充說明一下,我們看下面的圖,這是一個比較老的硬碟,在最外面的同心圓,他的半徑比裡面的同心圓的半徑要大,因為一個扇區儲存的物理空間是一定的,512B的資料,那麼裡面的同心圓資料更加密集,外面的就更加稀疏。在後來新的硬碟中就不是這麼處理的了,而是每個扇區都是等分的,也就是外圈的扇區數目比內圈扇區的數目要多。
還有一個特點,假設一個新的磁碟,裡面沒有任何資料,我們如果要在上面寫資料,它是由最外面的磁軌開始往裡寫的,(先不考慮刪除),就會產生一個問題,因為磁碟的旋轉,角速度是固定的,那外圈的線速度較大,同樣單位時間內,它滑的扇區就會更多,這樣的話,讀取外磁軌就比內磁軌要快。通過這裡的分析,大家應該能想到一個現象,就是剛買的電腦讀寫速度本來挺快的,但是用一段時間之後就變慢了,整理磁碟之後就會又快點,產生以上現象的原因就是因為磁碟是從外向裡寫的。當然,需要說明的一點,大家在學作業系統時書上應該也講過,磁碟空間的分配與回收,整理磁碟之後速度之所以會提升,除了剛才講的外磁軌速度快之外,還有一個原因就是,分配空間時,連續的物理空間速度回更快。
2.5磁碟存取特性
上面的內容我們瞭解了磁碟的結構,那麼我們繼續來看一下磁碟到底是怎麼讀取資料的。
它主要有3個步驟:
1.第一步,磁碟控制器將磁頭組合先定位到正確的柱面上。
2.第二步,磁碟是旋轉的,將磁碟旋轉到正確的扇區的起點。
3.第三步,就相應的扇區起點開始往後讀。
這三步,分別產生了三個時間,尋道時間、旋轉延遲、傳輸時間。這三個時間的總和就是磁碟的延遲。
上面我們講從磁碟讀取一個位元組的時間是10ms,那麼我們接下來看一下是怎麼算出來的。
已某磁碟為例,在柱面之間移動磁頭考慮到慣性作用,還有機械臂本身的延遲,大約花費1ms,這個時間其實是可以接受的。
每移動4000個柱面就會又花費1ms,這樣磁碟移動一個柱面的時間就是1.00025ms。
從最內圈到最外圈,總共是65536個柱面,大約用時17.38ms。這裡我們取平均情況,理論值大概是三分之一,可能有同學會問,理論值是怎麼來的,在這裡我們花一點時間來說明,假設,最內圈標記為0,最外圈標記為L,實際的起點是X,終點是Y,那麼X是可以從0取到L的,Y也是從0取到L的,我們把X和Y可能的組合就是L*L,那麼值就是:最後通過積分預算,大概是三分之一。
我們在買電腦時,有個磁碟指標就是磁碟轉速,有5400轉,7200轉,這裡的單位是都是 轉/分鐘,我們這裡已7200轉為例,那麼旋轉一圈的時間就是8.33ms。當然考慮到實際情況,如果旋轉的一週的話,是最差情況的,也就是磁頭剛好跨過起點,又得旋轉一週才回起點。最好的情況是磁頭剛好停到起點上,這裡我們取平均情況,也就是旋轉半圈的時間,4.17ms。
假設我們取4個扇區為一個塊,傳送一個塊的時間大約是0.13ms。
我們取平均時間
我們取平均值,6.46+4.17+0.13 = 10.76
最後我們來看讀取位元組數是不是線性增加的,比如讀一個位元組的時間是10ms,那麼讀100個位元組的時間是不是就是1000ms,根據我們上面講的讀取特性,如果這100個位元組都在同一個塊中,那麼就和讀取一個位元組的時間是一樣的。所以這個時間顯然不是成倍增加。
2.6 I/O開銷的主導地位
我們剛才講到讀一次就需要10ms,其實這個時間對於資料庫來說是非常長的,所以我們執行磁碟讀寫所花費的時間比用於操作記憶體中資料所要花的時間要長的多,這樣塊的訪問(I/O的次數),其實就是演算法所需的近似值。如果我之後要講查詢優化,那麼優化的標準是什麼,其實就是這裡的:對塊的訪問次數,次數越少,查詢越優。
下面有兩個例子,感興趣可以看一下,這裡不再展開詳細說了。
例1:資料庫有關係R,其中有對一元組的查詢,該元組有確定的鍵值K。最好建立一個索引來指向帶有鍵值K的元組存在哪個磁碟塊,而索引是否告訴我們該元組在塊的什麼位置是不重要的。
例2:上頁中我們討論的讀取一個塊的平均時間大約是11ms。而11ms對於一個現代微處理器,可以執行幾百萬條指令。如果塊在記憶體中,查詢鍵值為K的元組不過千餘條指令,在主存中搜索的時間不過是塊訪問時間的1%還要少,可以安全忽略之。
2.7組織磁碟上的資料-定長記錄
磁碟上的資料分為兩種,一種是定長記錄,另一種是非定長記錄。
下面我先說一下定長記錄。
最簡單的記錄由定長欄位組成,元組的每個屬性對應一個欄位。通常會根據情況要求欄位的起始地址是4或8的倍數,以便機器更有效率的讀寫。
通常記錄以記錄的首部開始,首部是關於記錄自身資訊的一個定長區域。例如我們要儲存如下資訊:
1.一個指向該記錄中儲存資料的模式的指標。例如:一個元組可以指向該元組所屬關係的模式,此資訊可以幫助我們找到記錄的欄位。
2.記錄欄位。此資訊幫助我們在不檢視記錄所屬關係的模式情況下,略過某些記錄。
3.時間戳。標識記錄最後一次修改或被讀的時間。有利於後面的事物
4.指向記錄的欄位的指標。此資訊可以代替模式資訊,在考慮變長欄位時,這個資訊格外重要。
我們看一個例子:
建立一張表,表結構如下。
|
按下圖所示,前面分別是模式指標,記錄的長度,還有時間戳,接下來開始存name,name是30個char,(不考慮字符集的問題,這裡認為一個char就佔用一個字符集的位元組,如果用utf8的話,1個2個3個位元組都有可能)這裡考慮一個位元組的情況下,30個字元就需要30個位元組,讀取的時候需要2的次冪的倍數來載入資料,比30大的最小整數倍就是32,所以這裡是從12到44。以此類推,adress欄位是char(255),,最小長度是256,所以從44到300。
2.8 組織磁碟上的資料-變長記錄
我們再看一下變長記錄是怎麼做的,我們把上表中name的欄位由char(30)改為varchar(30)。
|
如果一個記錄中含有變長欄位,那麼最有效的解決方法就是把所有定長的放前面,變長的欄位跟在定長欄位的後面。
但是這樣也存在一個問題,如果後面就只有一個變長欄位,也就無所謂了,但是如果有多個變長欄位怎麼辦呢。
因為第一個變長欄位我們知道起始位置,但是不知道有多長,對於第二個變長欄位,我們就需要一個指標,來指向第二個欄位的起始位置。如果後面還有更多的變長欄位,以此類推就可以了。
這裡有出現了一個新問題,因為我們不知道第一個變長欄位到底多長,那麼第二個變長欄位的指標應該加在什麼位置呢?
接下來我們解決這個問題,也就將要揭示出MySQL的理論基礎,我們把這個問題思考清楚了,再去看MySQL的設計,一下就會明白他為什麼要這麼做。
一邊我們存定長欄位,另外一邊存變長欄位,中間是空閒區,這樣做有什麼好處,中間及可以加指標,又可以填記錄,這就相當於雙向棧的形式。
2.9 組織磁碟上的資料-不能裝入一個塊中的記錄
上面我們講了變長記錄,其實考慮到變長記錄,這個長度是無法控制的,可能會很長,這裡也只用了varchar型別欄位,在mysql中varchar的最大限制是65535(後面我們會講這個限制有什麼含義),那麼對於text等型別,這個長度會更長,這種情況就產生了新的問題,對於這麼大的資料量,一個塊根本無法裝下,該怎麼解決呢?
解決方案也很簡單,分兩個塊去裝。
如下圖,記錄2在塊1中放不下了,那就再找一個塊,把記錄2的一部分放在塊1,另一部分放在塊2。當然需要加上指標和標記位,標記一下記錄的完整的記錄還是片段。如果是片段,就需要指標指向對應的片段,形成雙向連結串列形式。我們在遇到問題感覺很麻煩,但其實解決思想很簡單,在MySQL中也大量運用了這種解決思想,後面我們看具體的例子。
2.10 組織磁碟上的資料-記錄的插入
接下來我們如果想要插入一條記錄怎麼辦。
當一條記錄插入到一張表中時,如果這張表中資料沒有特定的儲存順序,我們就只需要隨便找一個塊,如果這個塊剛好有一段空閒空間,把這條記錄放進去就OK了。
但是MySQL中,儲存資料是必須要按照主鍵的順序來存放的,在這種情況就產生一個問題,如果我按順序插入了1、2、3條記錄,這個時候要插入記錄4時,是需要插到2和3中間的,按照下圖所示,我們就需要把記錄3往後移,把記錄4查到記錄2和 3中間,這樣就完成了按順序儲存的規定。
當然我們講的把記錄3往後移,是邏輯上的移動,並且,按照主鍵順序儲存也是邏輯上按順序儲存,而在物理空間上並不是按順序來儲存的,比如說,我們剛才講的把記錄3往後移,如果是真的在物理上的移動,那麼就是無謂的增加IO開銷。MySQL是實現時,將記錄1 2 3 4之間是用雙鏈表關聯起來的,物理空間上記錄還是放在記錄3的後面,只是指標指向了記錄2。
我想講到這裡大家應該會對主鍵自增有新的認識了吧,雖然在邏輯上記錄都是連續的,但是物理上是分離的,我們實際在使用MySQL進行查詢時,如果載入資料在物理空間上非連續,就會導致多次磁碟定址。這個IO的開銷還是特別大的,所以我們在設計表結構時,儘量讓主鍵自增,且是整型數字,儘量避免非數字型別當主鍵,一方面自增的記錄在插入時是在物理空間上連續的,節省磁碟定址時間,另一方面,整型是可以在暫存器中直接完成,而字串需要在記憶體中處理。
我們上面所講的都是在一個塊中存放記錄,我們之前講到讀寫都是以塊為基本單位,在說物理空間時,又說扇區是最小的讀寫單位,那麼這兩個概念的區別是什麼呢?
扇區是物理上的概念,塊是軟體層間的概念,一個塊大概是若干個扇區。
如果一個塊存不下這條記錄該怎麼辦?
一種方法是臨近塊,也就是再申請一個相鄰的塊。這種方案MySQL也採用了,但並不會特別好的解決問題。
另一種方案是溢位塊,塊的首部有個欄位來標記有一個溢位塊,並用指標指向存溢位塊的地址。溢位塊的頭部還可以指向下一個溢位塊。
3.InnoDB資料的儲存
接下來我們一看一下MySQL中最常用的儲存引擎,InnoDB,看一下它在做儲存時的思路
3.1邏輯架構
表空間→段 → 區 → 頁(塊) → 行記錄
表空間可以看做InnoDB儲存引擎的邏輯結構的最頂層。預設情況下,是共享表空間,一個數據庫中所有的表都在同一個表空間中,也可以設定成多表儲存空間,與此表有關的,索引、資料、快取、日誌等等一切,都存在此表空間中,這是邏輯上的儲存空間。
段是建立在表空間下面的概念。分為資料段、索引段、回滾段。這裡要說一下索引段,在InnoDB中,資料即索引,索引即資料。也就是說,在B+樹中,每個葉節點就是資料塊本身,而不是鍵值,所以在InnoDB中,資料塊和索引塊是沒有區別的。但是有的儲存引擎中,比如MyISAM,它的表空間是要建立三個檔案,資料段和索引段是分開的檔案。但是InnoDB中,資料和索引是放在同一個檔案中的。
區是由連續的頁組成的空間。任何情況下,一個區的大小是1M,在預設情況下,InnoDB一個塊的大小的16KB,也就是說,一個區預設有64個塊。我們看一下區的主要作用,在寫資料的過程中,一開始申請的空間有限,但是隨著插入資料越來越多,空間放不下了,這種情況下需要再向磁碟申請更多的塊,InnoDB的做法是一次就申請一個或者若干個區,不是一個頁一個頁的去申請。這裡我再思考一下,為什麼要用更大的單位去申請空間,其實就是我們上面講到的,在物理上的連續空間才能夠加快讀取資料。
頁又稱為塊,常見的有資料頁、系統頁、事物頁、資料頁、未壓縮的二進位制大物件頁、壓縮的二進位制大物件頁。這裡的二進位制大物件頁我們可以看成是text格式的欄位,也就是我們上面講到的溢位塊,只是名字改變了,設計思想還是一樣的。
3.2 表文件
聊完基礎概念,我們再來看錶檔案。
當我們建立完一個表之後,DBMS會建立了哪些檔案。
在InnoDB中,表中的資料和索引存放在同一個檔案中,表的結構單獨存放到另一個檔案中。當然還有回滾段,也是一個單獨的檔案。
3.3 行記錄
和大多數儲存引擎一樣,InnoDB使用頁作為磁碟管理的最小單位,資料在InnoDB中是按行儲存的,每個16KB的大小的頁中,可以存放2-200行的記錄。這裡需要說明的是,一個頁中最少要存放兩行記錄,原因是InnoDB的底層是B+樹,葉節點之間是通過指標相連的,如果一個節點中只有一條記錄,那麼就退化成了連結串列,所以最少兩條記錄。
當InnoDB儲存資料時,可以使用不同的行格式進行儲存。
兩種行記錄格式 compact 和 Redundant在磁碟上按照以下方式儲存:
1.在 Compact 中,行記錄的第一部分倒序存放了一行資料中列的長度(Length),而 Redundant 中存的是每一列的偏移量(Offset)
2.Compact 中對於NULL的儲存依靠NULL Indicator(向量),不佔用額外的空間。Redundant的VARCHAR型別的NULL不佔用空間,而CHAR型別的null佔用空間
總體上,Compact 行記錄格式相比 Redundant 格式能夠減少 20% 的儲存空間。
3.4 行溢位
頁大小預設是16KB,也可以設定為4KB、8KB、16KB和32KB時,行記錄最大長度應該略小於頁大小的一半,頁大小為64KB時,行記錄最大長度略小於16KB。這裡的原因同上面講到的一個頁中最少存放兩條記錄,防止退化成連結串列。如果在一個頁中不夠放兩條記錄,就會把部分資料溢位到另外的塊中。
對於上面介紹的兩種行格式,InnoDB都是把前768個位元組放在資料頁中,後面的資料通過偏移量存放到溢位頁中。
目前還有一種新的行格式 Compressed (這是我們資料庫目前在用的行格式)或者 Dynamic 時都只會在行記錄中儲存 20 個位元組的指標,實際的資料都會存放在溢位頁面中。這種處理方式有一個好處,因為整列資料都在溢位塊中,那麼就可以將整列資料做壓縮,之後如果有機會我們也可以討論一下資料中,資料的壓縮是怎麼做的。
下面我們來思考一個問題,如果我的表結構定義了很多列,並且都是varchar這種可變長度的,就算我們的每列都只放前768個位元組,那麼還是有可能所有列的前768個位元組加起來還是大於這個頁的長度。對於InnoDB來說,它的解決方案很暴力,就是不會讓你產生這種情況,它會報錯提示這一行過長,我們平時會經常遇到這一列過長的錯誤情況。
3.5 資料頁結構
頁是 InnoDB 儲存引擎管理資料的最小磁碟單位,這裡介紹頁是如何組織和儲存記錄的
一個 InnoDB 頁有以下七個部分:
每一個頁中包含了兩對 header/trailer:內部的 Page Header/Page Directory 關心的是頁的狀態資訊,而 Fil Header/Fil Trailer 關心的是記錄頁的頭資訊。
在頁的頭部和尾部之間就是使用者記錄和空閒空間了,每一個數據頁中都包含 Infimum 和 Supremum 這兩個虛擬的記錄(可以理解為佔位符),Infimum 記錄是比該頁中任何主鍵值都要小的值,Supremum 是該頁中的最大值。
User Records裡就是實際儲存的行記錄內容,Free Space指空閒空間,它也是個連結串列資料結構,當一條記錄被刪除時,該記錄的空間就會加入這個連結串列中。
為了保證插入和刪除的效率,整個頁面並不會按照主鍵順序對所有記錄進行排序,它會自動從左側向右尋找空白節點進行插入,行記錄在物理儲存上並不是按照順序的,它們之間的順序是由記錄頭部next_record 這一指標控制的。
B+ 樹在查詢對應的記錄時,並不會直接從樹中找出對應的行記錄,它只能獲取記錄所在的頁,將整個頁載入到記憶體中,再通過 Page Directory 中儲存的稀疏索引和 n_owned、next_record 屬性取出對應的記錄,不過因為這一操作是在記憶體中進行的,所以通常會忽略這部分查詢的耗時。
4最佳實戰
1.在能夠正確儲存的前提下,儘量選擇最小的資料型別,能用int就不用bigint,無符號代替有符號
2.簡單就好,整型比字串操作更低,使用mysql內建的型別,比如使用dateTime,不用字串
3.儘量避免使用null,因為查詢中使用null會更難優化,會使索引、索引統計更加複雜,當然也有例外情況,比如,業務中確定這些列可能為null,同時這些列也絕對不可能建立索引,比如地址。這種行記錄是由位向量來標記的,設定為可為null,儲存效率會更高。當然節約1列的意義不大,但是如果有很多列,節約出來的空間會特別大
5 後記
這次的介紹就這些,以後如果有機會,還會詳細介紹索引結構,查詢執行,故障對策,事物管理,並行與分散式資料庫,MySQL的記憶體管理,資料庫線上遷移以及我們平時在工作中遇到的典型案例。也希望 大家平時在工作注意收集踩過的坑,注意總結,提高自己的技術水平的同時,也能幫助其他同學一塊提高。