1. 程式人生 > >深入理解mysql的底層實現

深入理解mysql的底層實現


MySQL 的常用引擎

1. InnoDB

InnoDB 的儲存檔案有兩個,字尾名分別是 .frm 和 .idb,其中 .frm 是表的定義檔案,而 idb 是資料檔案。

InnoDB 中存在表鎖和行鎖,不過行鎖是在命中索引的情況下才會起作用。

InnoDB 支援事務,且支援四種隔離級別(讀未提交、讀已提交、可重複讀、序列化),預設的為可重複讀;而在 Oracle 資料庫中,只支援序列化級別和讀已提交這兩種級別,其中預設的為讀已提交級別。

2. Myisam

Myisam 的儲存檔案有三個,字尾名分別是 .frm、.MYD、MYI,其中 .frm 是表的定義檔案,.MYD 是資料檔案,.MYI 是索引檔案。

Myisam 只支援表鎖,且不支援事務。Myisam 由於有單獨的索引檔案,在讀取資料方面的效能很高 。

3. 儲存結構

InnoDB 和 Myisam 都是用 B+Tree 來儲存資料的。

MySQL 的資料、索引儲存結構

1. 資料儲存的原理(硬碟)

資訊儲存在硬盤裡,硬碟是由很多的碟片組成,通過碟片表面的磁性物質來儲存資料。

把碟片放在顯微鏡下放大,可以看到碟片表面是凹凸不平的,凸起的地方被磁化,代表數字 1,凹的地方沒有被磁化,代表數字 0,因此硬碟可以通過二進位制的形式來儲存表示文字、圖片等的資訊。

硬碟有很多種,但是都是由碟片、磁頭、碟片主軸、控制電機、磁頭控制器、資料轉換器、介面、快取等幾個部分組成。

所有的碟片都固定在一個旋轉軸上,這個軸即碟片主軸。

所有的碟片之間是絕對平行的,在每個碟片的盤面上都有一個磁頭,磁頭與碟片之間的距離比頭髮絲的直徑還小。

所有的磁頭連在一個磁頭控制器上,由磁頭控制器負責各個磁頭的運動,磁頭可沿碟片的半徑方向移動,實際上是斜切運動,每個磁頭同一時刻必須是同軸的,即從正上方往下看,所有磁頭任何時候都是重疊的。

由於技術的發展,目前已經有多磁頭獨立技術了,在此不考慮此種情況。

碟片以每分鐘數千轉到上萬轉的速度在高速運轉,這樣磁頭就能對碟片上的指定位置進行資料的讀寫操作。

由於硬碟是高精密裝置,塵埃是其大敵,所以必須完全密封。

2. 資料讀寫的原理

硬碟在邏輯上被劃分為磁軌、柱面以及扇區。

磁頭靠近主軸接觸的表面,即線速度最小的地方,是一個特殊的區域,它不存放任何資料,稱為啟停區或者著陸區,啟停區外就是資料區。

在最外圈,離主軸最遠的地方是 “0” 磁軌,硬碟資料的存放就是從最外圈開始的。

在硬碟中還有一個叫 “0” 磁軌檢測器的構件,它是用來完成硬碟的初始定位。

盤面

硬碟的碟片一般用鋁合金材料做基片,硬碟的每一個碟片都有上下兩個盤面,一般每個盤面都會得到利用,都可以儲存資料,成為有效盤面,也有極個別的硬碟盤面數為單數。

每一個這樣的有效盤面都有一個盤面號,按順序從上至下從 0 開始編號。

在硬碟系統中,盤面號又叫磁頭號,因為每一個有效盤面都有一個對應的讀寫磁頭,硬碟的碟片組在 2-14 片不等,通常有 2-3 個碟片。

磁軌

磁碟在格式化時被劃分成許多同心圓,這些同心圓軌跡叫做磁軌。

磁軌從外向內從 0 開始順序編號,硬碟的每一個盤面有 300-1024 個磁軌,新式大容量硬碟每面的磁軌數更多,資訊以脈衝串的形式記錄在這些軌跡中,這些同心圓不是連續記錄資料,而是被劃分成一段段的圓弧。

這些圓弧的角速度一樣,由於徑向長度不一樣,所以線速度也不一樣,外圈的線速度較內圈的線速度大,即同樣的轉速度下,外圈在同樣時間段裡,劃過的圓弧長度要比內圈劃過的圓弧長度大。

每段圓弧叫做一個扇區,扇區從 1 開始編號,每個扇區中的資料作為一個單元同時讀出或寫入。

磁軌是看不見的,只是盤面上以特殊形式磁化了的一些磁化區,在磁碟格式化時就已規劃完畢。

柱面

所有盤面上的同一磁軌構成一個圓柱,通常稱作柱面。

每個圓柱上的磁頭由上而下從 0 開始編號,資料的讀 / 寫按柱面進行,即磁頭讀 / 寫資料時首先在同一柱面內從 0 磁頭開始進行操作,依次向下在同一柱面的不同盤面即磁頭上進行操作。

只有在同一柱面所有的磁頭全部讀 / 寫完畢後磁頭才轉移到下一柱面(同心圓再往裡的柱面),因為選取磁頭只需要通過電子切換即可,而選取柱面則必須機械切換,電子切換相當快,比在機械上的磁頭向鄰近磁軌移動快得多。

所以,資料的讀 / 寫按柱面進行,而不按盤面進行,也就是說,一個磁軌寫滿資料後,就在同一柱面的下一個盤面來寫,一個柱面寫滿後,才移到下一個扇區開始寫資料,讀資料也按照這種方式進行,這樣就提高了硬碟的讀 / 寫效率。

扇區

作業系統以扇區形式將資訊儲存在硬碟上,每個扇區包括 512 個位元組的資料和一些其他資訊,一個扇區有兩個主要部分:儲存資料地點的識別符號和儲存資料的資料段。

識別符號就是扇區頭標,包括組成扇區三維地址的三個數字:盤面號,柱面號,扇區號(塊號)。

資料段可分為資料和保護資料的糾錯碼(ECC)。在初始準備期間,計算機用 512 個虛擬資訊位元組(實際資料的存放地)和與這些虛擬資訊位元組相應的 ECC 數字填入這個部分。

3. 訪盤請求完成過程

1)確定磁碟地址(柱面號,磁頭號,扇區號),記憶體地址(源 / 目):

當需要從磁碟讀取資料的時候,系統會將資料的邏輯地址傳遞個磁碟,磁碟的控制電路按照定址邏輯將邏輯地址翻譯成實體地址,即確定要讀的資料在哪個磁軌,哪個扇區。

2)為了讀取這個扇區的資料,需要將磁頭放到這個扇區上方,為了實現這一點:

  • A. 首先必須找到柱面,即磁頭需要移動對準相應磁軌,這個過程叫做尋道,所耗費時間叫做尋道時間。

  • B. 然後目標扇區旋轉到磁頭下,即磁碟旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。

3)即一次訪盤請求(讀 / 寫)完成過程由三個動作組成:

  • A. 尋道(時間):磁頭移動定位到指定磁軌。

  • B. 旋轉延遲(時間):等待指定扇區從磁頭下旋轉經過。

  • C. 資料傳輸(時間):資料在磁碟與記憶體之間的實際傳輸。

4. 磁碟的讀寫原理

系統將檔案儲存到磁碟上時,按柱面、磁頭、扇區的方式進行,即最先是第 1 磁軌的第一磁頭下的所有扇區,然後是同一柱面的下一個磁頭……

一個柱面儲存滿後就推進到下一個柱面,直到把檔案內容全部寫入磁碟。

系統也以相同的順序讀出資料,讀出資料時通過告訴磁碟控制器要讀出扇區所在柱面號、磁頭號和扇區號(實體地址的三個組成部分)進行。

5. 減少 I/O 的預讀原理

由於儲存介質的特性,磁碟本身存取就比主存慢很多,再加上機械運動耗費的時間,磁碟的存取速度往往是主存的幾百分之一。

因此,為了提高效率,要儘量減少磁碟的 I/O。

磁碟往往不是嚴格地按需讀取,而是每次都會預讀,即使只需要一個位元組,磁碟也會從這個位置開始,順序向後讀取一定長度的資料放入記憶體。

這樣做的理論依據是電腦科學中著名的區域性性原理:

  1. 當一個數據被用到時,其附近的資料一般來說也會被馬上使用。

  2. 程式執行期間所需要的資料通常比較集中。

  3. 由於磁碟順序讀取的效率很高(不需要尋道時間,只需要很少的旋轉時間),因此對於具有區域性性的程式來說,預讀可以提高 I/O 效率。

預讀的長度一般為頁(Page)的整數倍。頁是計算機管理儲存器的邏輯塊,硬體及作業系統往往將主存和磁碟儲存分割為連續的大小相等的塊。

每個儲存塊稱為一頁(在許多作業系統中,頁的大小通常為 4k),主存和磁碟以頁為單位交換資料,當程式要讀取的資料不在主存中時,會觸發一個缺頁異常。

此時系統會向磁碟發出讀盤資訊,磁碟會找到資料的起始位置並向後連續讀取一頁或幾頁的資料載入記憶體中,然後異常返回,程式繼續執行。

6. MySQL 的索引

索引是一種用來實現 MySQL 高效獲取資料的資料結構。

我們通常所說的在某個欄位上建索引,意思就是讓 MySQL 對該欄位以索引這種資料結構來儲存,然後查詢的時候就有對應的查詢演算法。

建索引的根本目的是為了查詢的優化,特別是當資料很龐大的時候,一般的查詢演算法有順序查詢、折半查詢、快速查詢等。

但是每種查詢演算法都只能應用於特定的資料結構之上,例如順序查詢依賴於順序結構,折半查詢通過二叉查詢樹或紅黑樹實現二分搜尋。因此在資料之外,資料庫系統還維護著滿足特定查詢演算法的資料結構。

這些資料結構以某種方式引用資料,這樣就可以在這些資料結構上實現高階查詢演算法,這種資料結構就是索引。

7. MySQL 的 B+Tree

目前大多數資料庫系統及檔案系統都採用 B-Tree 或其變種 B+Tree 作為索引結構。

B+ 樹索引是 B+ 樹在資料庫中的一種實現,是最常見也是資料庫中使用最為頻繁的一種索引。B+ 樹中的 B 代表平衡,而不是二叉。

因為 B+ 樹是從最早的平衡二叉樹演化而來的。B+ 樹是由二叉查詢樹、平衡二叉樹(AVLTree)和平衡多路查詢樹(B-Tree)逐步優化而來。

二叉查詢樹:左子樹的鍵值小於根的鍵值,右子樹的鍵值大於根的鍵值。

AVL 樹:平衡二叉樹(AVL 樹)在符合二叉查詢樹的條件下,還滿足任何節點的兩個子樹的高度最大差為 1。

平衡多路查詢樹(B-Tree):為磁碟等外儲存裝置設計的一種平衡查詢樹。

系統從磁碟讀取資料到記憶體時是以磁碟塊(block)為基本單位的,位於同一磁碟塊中的資料會被一次性讀取出來,而不是按需讀取。

InnoDB 儲存引擎使用頁作為資料讀取單位,頁是其磁碟管理的最小單位,預設 page 大小是 16k。

系統的一個磁碟塊的儲存空間往往沒有這麼大,因此 InnoDB 每次申請磁碟空間時都會是若干地址連續磁碟塊來達到頁的大小 16KB。

InnDB 在把磁碟資料讀入到磁碟時會以頁為基本單位,在查詢資料時如果一個頁中的每條資料都能助於定位資料記錄的位置,這將會減少磁碟 I/O 的次數,提高查詢效率。

B-Tree 結構的資料可以讓系統高效的找到資料所在的磁碟塊。

為了描述 B-Tree,首先定義一條資料記錄為一個二元組 [key, data],key 為記錄的鍵值,對於不同資料記錄,key 是互不相同的;data 為資料記錄除 key 外的資料。

那麼 B-Tree 是滿足下列條件的資料結構:

  1. d 為大於 1 的一個正整數,稱為 B-Tree 的度。

  2. h 為一個正整數,稱為 B-Tree 的高度。

  3. 每個非葉子節點由 n-1 個 key 和 n 個指標組成,其中 d<=n<=2d。

  4. 每個葉子節點最少包含一個 key 和兩個指標,最多包含 2d-1 個 key 和 2d 個指標,葉節點的指標均為 null 。

  5. 所有葉節點具有相同的深度,等於樹高 h。

  6. key 和指標互相間隔,節點兩端是指標。

  7. 一個節點中的 key 從左到右非遞減排列。

  8. 所有節點組成樹結構。

  9. 每個指標要麼為 null,要麼指向另外一個節點。

  10. 如果某個指標在節點 node 最左邊且不為 null,則其指向節點的所有 key 小於 v(key1),其中 v(key1) 為 node 的第一個 key 的值。

  11. 如果某個指標在節點 node 最右邊且不為 null,則其指向節點的所有 key 大於 v(keym),其中 v(keym) 為 node 的最後一個 key 的值。

  12. 如果某個指標在節點 node 的左右相鄰 key 分別是 keyi 和 keyi+1 且不為 null,則其指向節點的所有 key 小於 v(keyi+1) 且大於 v(keyi)。

B-Tree 中的每個節點根據實際情況可以包含大量的關鍵字資訊和分支,例:

?wx_fmt=png

每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的是子節點所在磁碟塊的地址。

兩個關鍵詞劃分成的三個範圍域對應三個指標指向的子樹的資料的範圍域。

以根節點為例,關鍵字為 17 和 35,P1 指標指向的子樹的資料範圍為小於 17,P2 指標指向的子樹的資料範圍為 17~35,P3 指標指向的子樹的資料範圍為大於 35。

模擬查詢關鍵字 29 的過程:

  1. 根據根節點找到磁碟塊 1,讀入記憶體。【磁碟 I/O 操作第 1 次】

  2. 比較關鍵字 29 在區間(17,35),找到磁碟塊 1 的指標 P2。

  3. 根據 P2 指標找到磁碟塊 3,讀入記憶體。【磁碟 I/O 操作第 2 次】

  4. 比較關鍵字 29 在區間(26,30),找到磁碟塊 3 的指標 P2。

  5. 根據 P2 指標找到磁碟塊 8,讀入記憶體。【磁碟 I/O 操作第 3 次】

  6. 在磁碟塊 8 中的關鍵字列表中找到關鍵字 29。

     

MySQL 的 InnoDB 儲存引擎在設計時是將根節點常駐記憶體的,因此力求達到樹的深度不超過 3,也就是說 I/O 不需要超過 3 次。

分析上面過程,發現需要 3 次磁碟 I/O 操作,和 3 次記憶體查詢操作。由於記憶體中的關鍵字是一個有序表結構,可以利用二分法查詢提高效率。

而 3 次磁碟 I/O 操作是影響整個 B-Tree 查詢效率的決定因素。

B-Tree 相對於 AVLTree 縮減了節點個數,使每次磁碟 I/O 取到記憶體的資料都發揮了作用,從而提高了查詢效率。

B+Tree 是在 B-Tree 基礎上的一種優化,使其更適合實現外儲存索引結構,InnoDB 儲存引擎就是用 B+Tree 實現其索引結構。

在 B-Tree 中,每個節點中有 key,也有 data,而每一個頁的儲存空間是有限的,如果 data 資料較大時將會導致每個節點(即一個頁)能儲存的 key 的數量很小。

當儲存的資料量很大時同樣會導致 B-Tree 的深度較大,增大查詢時的磁碟 I/O 次數,進而影響查詢效率。

在 B+Tree 中,所有資料記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只儲存 key 值資訊,這樣可以大大加大每個節點儲存的 key 值數量,降低 B+Tree 的高度。

B+Tree 在 B-Tree 的基礎上有兩點變化:

  1. 資料是存在葉子節點中的;

  2. 資料節點之間是有指標指向的。

     

由於 B+Tree 的非葉子節點只儲存鍵值資訊,假設每個磁碟塊能儲存 4 個鍵值及指標資訊,則變成 B+Tree 後其結構如下圖所示:

?wx_fmt=png

通常在 B+Tree 上有兩個頭指標,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即資料節點)之間是一種鏈式環結構。

因此可以對 B+Tree 進行兩種查詢運算:一種是對於主鍵的範圍查詢和分頁查詢,另一種是從根節點開始,進行隨機查詢。

8. Myisam 中的 B+Tree

Myisam 引擎也是採用的 B+Tree 結構來作為索引結構。

由於 Myisam 中的索引和資料分別存放在不同的檔案,所以在索引樹中的葉子節點中存的資料是該索引對應的資料記錄的地址,由於資料與索引不在一起,所以 Myisam 是非聚簇索引。

?wx_fmt=png

9. InnoDB 中的 B+Tree

InnoDB 是以 ID 為索引的資料儲存。

採用 InnoDB 引擎的資料儲存檔案有兩個,一個定義檔案,一個是資料檔案。

InnoDB 通過 B+Tree 結構對 ID 建索引,然後在葉子節點中儲存記錄。

?wx_fmt=png

若建索引的欄位不是主鍵 ID,則對該欄位建索引,然後在葉子節點中儲存的是該記錄的主鍵,然後通過主鍵索引找到對應的記錄。

MySQL 的相關優化

1. MySQL 效能優化:組成、表的設計

  1. 開啟查詢快取。避免某些 SQL 函式直接在 SQL 語句中使用,從而導致 Mysql 快取失效。

  2. 避免畫蛇添足。目的是什麼就取什麼,例如某個邏輯是隻需要判斷是否存在女性,若是查到了一條即可,勿要全部都查一遍,此時要善用 limit。

  3. 建合適的索引。所以要建在合適的地方,合適的物件上。經常操作 / 比較 / 判斷的欄位應該建索引。

  4. 欄位大小合宜。欄位的取值是有限而且是固定的,這種情況下可以用 enum,IP 欄位可以用 unsigned int 來儲存。

  5. 表的設計。垂直分割表,使得固定表與變長表分割,從而降低表的複雜度和欄位的數目。

2. SQL 語句優化:避免全表掃描

  1. 建索引:一般在 where 及 order by 中涉及到的列上建索引,儘量不要對可以重複的欄位建索引。

  2. 儘量避免在 where 中使用 !(<>)或 or,也不要進行 null 值判斷。

  3. 儘量避免在 where 中對欄位進行函式操作、表示式操作。

  4. 儘量避免使用 like- %,在此種情況下可以進行全文檢索。

近期熱文

這樣做,你的面試成功率將達到 90%

如何用 TensorFlow 讓一切看起來更美?

Web 安全:前端攻擊 XSS 深入解析

300萬粉絲,全國最大的線上抽獎平臺,深度解析

高可用、高效能? 介面設計的 16 個原則

 

 

?wx_fmt=jpeg

「閱讀原文」看交流實錄,你想知道的都在這裡