1. 程式人生 > 實用技巧 >深入理解mysql索引的底層資料結構

深入理解mysql索引的底層資料結構

轉自:一角錢技術 https://blog.csdn.net/org_hjh/article/details/108553522

前言

MySQL官方提到,改善操作效能的最佳方法[SELECT](https://dev.mysql.com/doc/refman/5.7/en/select.html)在查詢中測試的一個或多個列上建立索引。索引條目的作用類似於指向錶行的指標,從而使查詢可以快速確定哪些行與WHERE子句中的條件匹配,並檢索這些行的其他列值。所有MySQL資料型別都可以建立索引。

儘管可能會為查詢中使用的每個可能的列建立索引,但不必要的索引會浪費空間和時間,使MySQL難以確定要使用的索引。索引還會增加插入,更新和刪除的成本,因為必須更新每個索引。您必須找到適當的平衡,才能使用最佳索引集來實現快速查詢。

那麼,索引到底是什麼?透過現象看本質:

MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構。索引的本質:索引是資料結構。

另外,阿里巴巴《Java 開發手冊》提出單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。對此,有阿里的黃金鐵律支撐,所以,很多人設計大資料儲存時,多會以此為標準,進行分表操作。

以及,阿里巴巴《Java 開發手冊》補充到:如果預計三年後的資料量根本達不到這個級別,請不要在建立表時就分庫分表。

為了更深入理解索引的本質,這裡我們先了解一下磁碟相關知識。

外儲存器-磁碟

計算機一般有兩種儲存的方式:記憶體儲器(main memory)和外儲存器(external memory)

  • 記憶體:讀寫速度非常快,但是容量很小,而且造價非常貴,在不通電的情況下會資料會丟失,不能長期儲存資料;
  • 外存:磁碟是相對常見的外儲存裝置,它是以存取時間變化不大為特徵的。可以直接存取任何字元組,且容量大、速度較其它外存裝置更快。

磁碟的構造

磁碟是一個扁平的圓盤(與電唱機的唱片類似)。盤面上有許多稱為磁軌的圓圈,資料就記錄在這些磁軌上。磁碟可以是單片的,也可以是由若干碟片組成的盤組,每一碟片上有兩個面。

當磁碟驅動器執行讀/寫功能時。碟片裝在一個主軸上,並繞主軸高速旋轉,當磁軌在讀/寫頭(又叫磁頭) 下通過時,就可以進行資料的讀/寫了。

一般磁碟分為固定頭盤(磁頭固定)和活動頭盤。

固定頭盤的每一個磁軌上都有獨立的磁頭,它是固定不動的,專門負責這一磁軌上資料的讀/寫。

活動頭盤的磁頭是可移動的。每一個盤面上只有一個磁頭(磁頭是雙向的,因此正反盤面都能讀寫)。它可以從該面的一個磁軌移動到另一個磁軌,所有磁頭都裝在同一個動臂上,因此不同盤面上的所有磁頭都是同時移動的(行動整齊劃一),當碟片繞主軸旋轉的時候,磁頭與旋轉的碟片形成一個圓柱體,各個盤面上半徑相同的磁軌組成了一個圓柱面,我們稱為柱面。因此,柱面的個數也就是盤面上的磁軌數。

磁碟的讀/寫原理和效率

磁碟上資料必須用一個三維地址唯一標示:柱面號、盤面號、塊號(磁軌上的盤塊)。

讀/寫磁碟上某一指定資料需要下面3個步驟:

  1. 首先移動臂根據柱面號使磁頭移動到所需要的柱面上,這一過程被稱為定位或查詢。
  2. 如上圖6盤組示意圖中,所有磁頭都定位到了10個盤面的10條磁軌上(磁頭都是雙向的),這時根據盤面號來確定指定盤面上的磁軌。
  3. 盤面確定以後,碟片開始旋轉,將指定塊號的磁軌段移動至磁頭下。

經過上面三個步驟,指定資料的儲存位置就被找到,這時就可以開始讀/寫操作了。

訪問某一具體資訊,由3部分時間組成:

  • 查詢時間(seek time) Ts: 完成上述步驟(1)所需要的時間。這部分時間代價最高,最大可達到0.1s左右;
  • 等待時間(latency time) Tl: 完成上述步驟(3)所需要的時間。由於碟片繞主軸旋轉速度很快,一般為7200轉/分(電腦硬碟的效能指標之一, 家用的普通硬碟的轉速一般有5400rpm(筆記本)、7200rpm幾種),因此一般旋轉一圈大約0.0083s
  • 傳輸時間(transmission time) Tt: 資料通過系統匯流排傳送到記憶體的時間,一般傳輸一個位元組(byte)大概0.02us=2*10^(-8)s

尋道時間Ts :
    T s = m ∗ n + s Ts = m * n + sTs=mn+s

n : 跨越n條磁軌的時間; s: 啟動磁臂的時間,約為2ms ; m:與磁碟驅動器速度有關的常數,約為0.2ms。

延遲時間Tr :
    T r = 1 / ( 2 ∗ r ) Tr = 1 / (2 * r)Tr=1/2r

r : 磁碟的旋轉速度

傳輸時間Tt :
    T t = b / ( r ∗ N ) Tt = b / (r * N)Tt=b/rN

r : 磁碟的旋轉速度; N:為一個磁軌上的位元組數;b:每次所讀/寫的位元組數b

總平均存取時間 :
  T a = T s + T r + T t Ta = Ts + Tr + TtTa=Ts+Tr+Tt

磁碟讀取資料是以盤塊(block)為基本單位的。位於同一盤塊中的所有資料都能被一次性全部讀取出來。而磁碟IO代價主要花費在查詢時間Ts上,因此我們應該儘量將相關資訊存放在同一盤塊,同一磁軌中,或者至少放在同一柱面或相鄰柱面上,以求在讀/寫資訊時儘量減少磁頭來回移動的次數,避免過多的查詢時間Ts。

所以,在大規模資料儲存方面,大量資料儲存在外存磁碟中,而在外存磁碟中讀取/寫入塊(block)中某資料時,首先需要定位到磁碟中的某塊,如何有效地查詢磁碟中的資料,需要一種合理高效的外存資料結構。

索引的本質

索引是幫助MySQL高效獲取資料的排好序的資料結構

索引資料結構,主要包含以下幾類,我們來對比下

  • 二叉樹
  • 平衡二叉樹
  • 紅黑樹
  • Hash表
  • B-Tree

二叉樹

定義:每個結點最多有兩個子樹,左子樹比父節點小,右子樹比父節點大。

缺點:會出現極端情況導致整棵樹只有左子樹或只有右子樹。

平衡二叉樹(AVL Tree)

定義:平衡二叉樹又稱AVL樹,是一種特殊的二叉查詢樹,其左右子數都是平衡二叉樹,且左右子樹高度差的絕對值不超過1。

缺點:AVL樹是高度平衡的,頻繁的插入和刪除,會引起頻繁的rebalance,導致效率下降。

紅黑樹

定義:

  • 性質1. 節點是紅色或黑色。
  • 性質2. 根節點是黑色。
  • 性質3 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
  • 性質4. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

缺點:資料量大會導致樹層數比較多,這樣就會造成查詢資料慢。

Hash資料結構

定義:散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。 對目標值進行hash運算得到hash值和資料磁碟指標地址儲存到hash表,這樣就達到快速定位資料位置。

缺點:精確查詢十分快速,但範圍查詢就碰壁了。

BTree

定義:

  • 一個節點可以儲存多個數據,這樣可以避免黑紅樹的缺點,樹的層數很變小;
  • 葉節點具有相同的深度,葉節點的指標為空;
  • 所有索引元素不重複;
  • 節點中的資料索引從左到右遞增排列。

缺點:節點裡面數組資料:每個資料的結構=索引資料+資料記錄(即葉子節點儲存鍵值和資料記錄)。

每個節點佔用一個盤塊的磁碟空間,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指標,指標儲存的是子節點所在磁碟塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指標指向的子樹的資料的範圍域。以根節點為例,關鍵字為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。

分析上面過程,發現需要3次磁碟I/O操作,和3次記憶體查詢操作。由於記憶體中的關鍵字是一個有序表結構,可以利用二分法查詢提高效率。而3次磁碟I/O操作是影響整個B-Tree查詢效率的決定因素。B-Tree相對於AVLTree縮減了節點個數,使每次磁碟I/O取到記憶體的資料都發揮了作用,從而提高了查詢效率。

B+Tree

定義:B+Tree是在B-Tree基礎上的一種優化。節點裡面數組資料:每個資料只儲存鍵資訊,這樣不存資料可以騰出空間放更多的鍵資訊,讓樹層數越小

  • 非葉子節點不儲存data,只儲存索引(冗餘),可以放更多的索引
  • 葉子節點包含所有索引欄位
  • 葉子節點用指標連線,提高區間訪問的效能

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

通常在B+Tree上有兩個頭指標,一個指向根節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即資料節點)之間是一種鏈式環結構。因此可以對B+Tree進行兩種查詢運算:一種是對於主鍵的範圍查詢和分頁查詢,另一種是從根節點開始,進行隨機查詢。

可能上面例子中只有22條資料記錄,看不出B+Tree的優點,下面做一個推算:

InnoDB儲存引擎中頁的大小為16KB,一般表的主鍵型別為INT(佔用4個位元組)或BIGINT(佔用8個位元組),指標型別也一般為4或8個位元組,也就是說一個頁(B+Tree中的一個節點)中大概儲存16KB/(8B+8B)=1K個鍵值。(因為是估值,為方便計算,這裡的K取值為〖10〗3)。也就是說一個深度為3的B+Tree索引可以維護103 * 10^3 * 10^3 = 10億 條記錄。

實際情況中每個節點可能不能填充滿,因此在資料庫中,B+Tree的高度一般都在24層。MySQL的InnoDB儲存引擎在設計時是將根節點常駐記憶體的,也就是說查詢某一鍵值的行記錄時最多隻需要13次磁碟I/O操作。

資料庫中的B+Tree索引可以分為聚集索引(clustered index)和 輔助索引(secondary index)。上面的B+Tree示例圖在資料庫中的實現即為聚集索引,聚集索引的B+Tree中的葉子節點存放的是整張表的行記錄資料。

輔助索引與聚集索引的區別在於輔助索引的葉子節點並不包含行記錄的全部資料,而是儲存相應行資料的聚集索引鍵,即主鍵。當通過輔助索引來查詢資料時,InnoDB儲存引擎會遍歷輔助索引找到主鍵,然後再通過主鍵在聚集索引中找到完整的行記錄資料。

檢視mysql檔案頁大小(16K):SHOW GLOBAL STATUS like 'Innodb_page_size’;

MySQL儲存引擎

什麼是儲存引擎?

資料庫儲存引擎是資料庫底層軟體元件,資料庫管理系統使用資料引擎進行建立、查詢、更新和刪除資料操作。不同的儲存引擎提供不同的儲存機制、索引技巧、鎖定水平等功能,使用不同的儲存引擎還可以獲得特定的功能。

現在許多資料庫管理系統都支援多種不同的儲存引擎。MySQL 的核心就是儲存引擎。

  • InnoDB 事務型資料庫的首選引擎,支援事務安全表(ACID),支援行鎖定和外來鍵。MySQL 5.5.5 之後,InnoDB 作為預設儲存引擎。
  • MyISAM 是基於 ISAM 的儲存引擎,並對其進行擴充套件,是在 Web、資料倉儲和其他應用環境下最常使用的儲存引擎之一。MyISAM 擁有較高的插入、查詢速度,但不支援事務。
  • MEMORY 儲存引擎將表中的資料儲存到記憶體中,為查詢和引用其他資料提供快速訪問。

MySQL 5.7 支援的儲存引擎

MySQL支援多種型別的資料庫引擎,可分別根據各個引擎的功能和特性為不同的資料庫處理任務提供各自不同的適應性和靈活性。在 MySQL 中,可以利用SHOW ENGINES語句來顯示可用的資料庫引擎和預設引擎。

MySQL提供了多個不同的儲存引擎,包括處理事務安全表的引擎和處理非事務安全表的引擎。在 MySQL 中,不需要在整個伺服器中使用同一種儲存引擎,針對具體的要求,可以對每一個表使用不同的儲存引擎。

MySQL 5.7 支援的儲存引擎有 InnoDB、MyISAM、Memory、Merge、Archive、Federated、CSV、BLACKHOLE 等。

可以使用SHOW ENGINES語句檢視系統所支援的引擎型別,結果如圖所示。

如何選擇 MySQL 儲存引擎

不同的儲存引擎都有各自的特點,以適應不同的需求,如表所示。為了做出選擇,首先要考慮每一個儲存引擎提供了哪些不同的功能。

功能MylSAMMEMORYInnoDBArchive
儲存限制 256TB RAM 64TB None
支援事務 No No Yes No
支援全文索引 Yes No No No
支援樹索引 Yes Yes Yes No
支援雜湊索引 No Yes No No
支援資料快取 No N/A Yes No
支援外來鍵 No No Yes No

可以根據以下的原則來選擇 MySQL 儲存引擎:

  • 如果要提供提交、回滾和恢復的事務安全(ACID 相容)能力,並要求實現併發控制,InnoDB 是一個很好的選擇。
  • 如果資料表主要用來插入和查詢記錄,則 MyISAM 引擎提供較高的處理效率。
  • 如果只是臨時存放資料,資料量不大,並且不需要較高的資料安全性,可以選擇將資料儲存在記憶體的 MEMORY 引擎中,MySQL 中使用該引擎作為臨時表,存放查詢的中間結果。
  • 如果只有 INSERT 和 SELECT 操作,可以選擇Archive 引擎,Archive 儲存引擎支援高併發的插入操作,但是本身並不是事務安全的。Archive 儲存引擎非常適合儲存歸檔資料,如記錄日誌資訊可以使用 Archive 引擎。

提示:使用哪一種引擎要根據需要靈活選擇,一個數據庫中多個表可以使用不同的引擎以滿足各種效能和實際需求。使用合適的儲存引擎將會提高整個資料庫的效能。

使用下面的語句可以修改資料庫臨時的預設儲存引擎
SET default_storage_engine=< 儲存引擎名 >
注意:將 MySQL 資料庫的臨時預設儲存引擎修改為 其他的儲存引擎時 ,但是當再次重啟客戶端時,預設儲存引擎仍然是 InnoDB。

MyISAM儲存引擎

資料儲存形式

MyISAM採用的是索引與資料分離的形式,將資料儲存在三個檔案中.frm.MYD.MYI

  • .frm : 儲存表結構
  • .MYD : 儲存表資料
  • .MYI :儲存表索引

鎖的粒度

MyISAM不支援行鎖,所以讀取時對錶加上共享鎖,在寫入是對錶加上排他鎖。由於是對整張表加鎖,相比InnoDB,在併發寫入時效率很低。

事務

MyISAM不支援事務。

資料的儲存特點

MyISAM是基於非聚簇索引進行儲存的。

索引實現

MyISAM索引檔案和資料檔案是分離的(非聚集)

其他

MyISAM提供了大量的特性,包括全文索引,壓縮,空間函式,延遲更新索引鍵等。

  • 進行壓縮後的表是不能進行修改的,但是壓縮表可以極大減少磁碟佔用空間,因此也可以減少磁碟IO,從而提供查詢效能。

  • 全文索引,是一種基於分詞建立的索引,可以支援複雜的查詢。

  • 延遲更新索引鍵,不會將更新的索引資料立即寫入到磁碟,而是會寫到記憶體中的緩衝區中,只有在清除緩衝區時候才會將對應的索引寫入磁碟,這種方式大大提升了寫入效能。

InnoDB儲存引擎

資料儲存形式

使用InnoDB時,會將資料表分為.frm.ibd兩個檔案進行儲存。

  • .frm : 儲存表結構
  • .ibd : 儲存表資料和索引

innodb的所有資料檔案(字尾為ibd的檔案),他的大小始終都是16384(16k)的整數倍。

鎖的粒度

InnoDB採用**MVCC(多版本併發控制)**來支援高併發,InnoDB實現了四個隔離級別,預設級別是REPETABLE READ,並通過間隙鎖策略防止幻讀的出現。它的鎖粒度是行鎖。【MVCC在稍後會進行介紹】

事務

InnoDB是典型的事務型儲存引擎,並且通過一些機制和工具,支援真正的熱備份。

資料的儲存特點

InnoDB表是基於聚簇索引建立的,聚簇索引對主鍵的查詢有很高的效能,不過他的二級索引(非主鍵索引)必須包含主鍵列,索引其他的索引會很大。

索引實現

InnoDB索引實現(聚簇)

  • 表資料檔案本身就是按B+Tree組織的一個索引結構檔案
  • 聚簇索引-葉節點包含了完整的資料記錄
  • 為什麼InnoDB表必須有主鍵,並且推薦使用整型的自增主鍵?
  • 為什麼非主鍵索引結構葉子節點儲存的是主鍵值?(一致性和節省儲存空間)

聯合索引

聯合索引的底層儲存結構長什麼樣?

比較相等時,先比較第一列的值,如果相等,再繼續比較第二列,以此類推。

最左字首原理

使用聯合索引時,索引列的定義順序將會影響到最終查詢時索引的使用情況。例如聯合索引(a,b,c),mysql會從最左邊的列優先匹配,如果最左邊的帶頭大哥沒有使用到,在未使用覆蓋索引的情況下,就只能全表掃描。
聯合底層資料結構思考,mysql會優先以聯合索引第一列匹配,此後才會匹配下一列,如果不指定第一列匹配的值,也就無法得知下一步查詢哪個節點。
另外還有一種情況,如果遇到 > < between等這樣的範圍查詢,那B+樹中也就無法對下一列進行等值匹配了。

淺談MVCC

MySQL大多數事務型儲存引擎實現的都不是簡單的行鎖。基於提升併發效能的考慮,他們一般都同時實現了多版本併發控制(MVCC)。

可以認為MVCC是行級鎖的一個變種,它能在大多數情況下避免加鎖操作,因此開銷更低。無論怎樣實現,它們大都實現了非阻塞的讀操作,寫操作也只鎖定製定的行。

MVCC是通過儲存資料在某一個時間點的快照來實現的,也就是說無論事務執行多久,每個事務看到的資料都是一致的。InnoDB的MVCC,是通過在每行記錄後面儲存兩個隱藏的列來實現,這兩個列一個儲存了行的建立時間,一個儲存了行的過期時間(或刪除時間),當然,並非儲存的是時間,而是系統版本號。每開啟一個事務,版本號都會遞增,事務開始時刻的系統版本號會作為事務的版本號。

idname建立時間(行版本號)刪除時間(刪除版本號)
1 Mary 1 null
2 Jann 1 null

以InnoDB儲存引擎的的REPEATABLE READ隔離級別來說:

SELECT

  • 只查詢建立時間版本號小於當前事務版本號的資料行(保證事務讀取的行要麼在事務開始之前就存在,要麼是事務本身插入的行)
  • 行的刪除版本號要麼未定義,要麼大於當前事務版本號,這樣可以確保事務讀取到的行,在開始事務之前未被刪除

只有複合上訴兩個條件的記錄才會作為結果返回

INSERT

為插入的資料儲存當前系統版本號作為行版本號

DELETE

儲存當前系統版本號作為刪除行版本號

UPDATE

插入一行資料,並將當前系統版本號賦予行版本號;同時儲存當前系統版本號到原來的行作為刪除版本號。

注:MVCC只在REPEATABLE和READ COMMITTED兩個隔離級別下才能正常工作。

問題總結

InnoDB一棵B+樹可以存放多少行資料?

這個問題的簡單回答是:約2千萬。為什麼是這麼多呢?因為這是可以算出來的,要搞清楚這個問題,我們先從InnoDB索引資料結構、資料組織方式說起。

我們都知道計算機在儲存資料的時候,有最小儲存單元,這就好比我們今天進行現金的流通最小單位是一毛。在計算機中磁碟儲存資料最小單元是扇區,一個扇區的大小是512位元組,而檔案系統(例如XFS/EXT4)他的最小單元是塊,一個塊的大小是4k,而對於我們的InnoDB儲存引擎也有自己的最小儲存單元——頁(Page),一個頁的大小是16K。

下面幾張圖可以幫你理解最小儲存單元:

檔案系統中一個檔案大小隻有1個位元組,但不得不佔磁碟上4KB的空間。

innodb的所有資料檔案(字尾為ibd的檔案),他的大小始終都是16384(16k)的整數倍。

磁碟扇區、檔案系統、InnoDB儲存引擎都有各自的最小儲存單元。

在MySQL中我們的InnoDB頁的大小預設是16k,當然也可以通過引數設定:

mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

資料表中的資料都是儲存在頁中的,所以一個頁中能儲存多少行資料呢?假設一行資料的大小是1k,那麼一個頁可以存放16行這樣的資料。

如果資料庫只按這樣的方式儲存,那麼如何查詢資料就成為一個問題,因為我們不知道要查詢的資料存在哪個頁中,也不可能把所有的頁遍歷一遍,那樣太慢了。所以人們想了一個辦法,用B+樹的方式組織這些資料。如圖所示:

我們先將資料記錄按主鍵進行排序,分別存放在不同的頁中(為了便於理解我們這裡一個頁中只存放3條記錄,實際情況可以存放很多),除了存放資料的頁以外,還有存放鍵值+指標的頁,如圖中page number=3的頁,該頁存放鍵值和指向資料頁的指標,這樣的頁由N個鍵值+指標組成。當然它也是排好序的。這樣的資料組織形式,我們稱為索引組織表。現在來看下,要查詢一條資料,怎麼查?

select * from user where id=5;

這裡id是主鍵,我們通過這棵B+樹來查詢,首先找到根頁,你怎麼知道user表的根頁在哪呢?其實每張表的根頁位置在表空間檔案中是固定的,即page number=3的頁(這點我們下文還會進一步證明),找到根頁後通過二分查詢法,定位到id=5的資料應該在指標P5指向的頁中,那麼進一步去page number=5的頁中查詢,同樣通過二分查詢法即可找到id=5的記錄.

現在我們清楚了InnoDB中主鍵索引B+樹是如何組織資料、查詢資料的,總結一下:

  1. InnoDB儲存引擎的最小儲存單元是頁,頁可以用於存放資料也可以用於存放鍵值+指標,在B+樹中葉子節點存放資料,非葉子節點存放鍵值+指標。
  2. 索引組織表通過非葉子節點的二分查詢法以及指標確定資料在哪個頁中,進而在去資料頁中查詢到需要的資料;

那麼回到我們開始的問題,通常一棵B+樹可以存放多少行資料?

這裡我們先假設B+樹高為2,即存在一個根節點和若干個葉子節點,那麼這棵B+樹的存放總記錄數為:根節點指標數*單個葉子節點記錄行數。

上文我們已經說明單個葉子節點(頁)中的記錄數=16K/1K=16。(這裡假設一行記錄的資料大小為1k,實際上現在很多網際網路業務資料記錄大小通常就是1K左右)。

那麼現在我們需要計算出非葉子節點能存放多少指標,其實這也很好算,我們假設主鍵ID為bigint型別,長度為8位元組,而指標大小在InnoDB原始碼中設定為6位元組,這樣一共14位元組,我們一個頁中能存放多少這樣的單元,其實就代表有多少指標,即16384/14=1170。那麼可以算出一棵高度為2的B+樹,能存放1170*16=18720條這樣的資料記錄。

根據同樣的原理我們可以算出一個高度為3的B+樹可以存放:1170*1170*16=21902400條這樣的記錄。所以在InnoDB中B+樹高度一般為1-3層,它就能滿足千萬級的資料儲存。在查詢資料時一次頁的查詢代表一次IO,所以通過主鍵索引查詢通常只需要1-3次IO操作即可查詢到資料。

怎麼得到InnoDB主鍵索引B+樹的高度?

上面我們通過推斷得出B+樹的高度通常是1-3,下面我們從另外一個側面證明這個結論。在InnoDB的表空間檔案中,約定page number為3的代表主鍵索引的根頁,而在根頁偏移量為64的地方存放了該B+樹的page level。如果page level為1,樹高為2,page level為2,則樹高為3。即B+樹的高度=page level+1;下面我們將從實際環境中嘗試找到這個page level。

在實際操作之前,你可以通過InnoDB元資料表確認主鍵索引根頁的page number為3,你也可以從《InnoDB儲存引擎》這本書中得到確認。

SELECT
b.name, a.name, index_id, type, a.space, a.PAGE_NO
FROM
information_schema.INNODB_SYS_INDEXES a,
information_schema.INNODB_SYS_TABLES b
WHERE
a.table_id = b.table_id AND a.space <> 0;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

執行結果:

可以看出資料庫approval_db下的sys_config表、sys_config表主鍵索引根頁的page number均為3,而其他的二級索引page number為4。

聚簇索引和非聚簇索引的區別

  • 聚簇索引: 將資料儲存與索引放到了一塊,索引結構的葉子節點儲存了行資料
  • 非聚簇索引:將資料與索引分開儲存,索引結構的葉子節點指向了資料對應的位置

為什麼InnoDB表必須有主鍵,並且推薦使用整型的自增主鍵?

  1. 如果設定了主鍵,那麼InnoDB會選擇主鍵作為聚集索引,如果沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的唯一索引作為主鍵索引、如果也沒有這樣的唯一索引,則InnoDB會選擇內建6位元組長的ROWID作為隱含的聚集索引(ROWID隨著行記錄的寫入而主鍵遞增)。
  2. 如果表使用自增主鍵

那麼每次插入新的記錄,記錄就會順序新增到當前索引節點的後續位置,主鍵的順序按照資料記錄的插入順序排列,自動有序。當一頁寫滿,就會自動開闢一個新的頁

  1. 如果使用非自增主鍵(如果身份證號或學號等)

由於每次插入主鍵的值近似於隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置,此時MySQL不得不為了將新記錄插到合適位置而移動資料,甚至目標頁面可能已經被回寫到磁碟上而從快取中清掉,此時又要從磁碟上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,後續不得不通過OPTIMIZE TABLE來重建表並優化填充頁面。

MySQL為什麼用整型自增作為索引比較好。而UUID作為索引效率比較低?

聚簇索引的資料的物理存放順序與索引順序是一致的,即:只要索引是相鄰的,那麼對應的資料一定也是相鄰地存放在磁碟上的。如果主鍵不是自增id,那麼可以想象,它會幹些什麼,不斷地調整資料的實體地址、分頁,當然也有其他一些措施來減少這些操作,但卻無法徹底避免。但,如果是自增的,那就簡單了,它只需要一頁一頁地寫,索引結構相對緊湊,磁碟碎片少,效率也高。

  • 索引儲存在磁碟,而且樹的每個節點分配的空間有大小。整型佔空間比較小,這樣可以存放多個鍵值。反之然後UUID佔空間比較大。
  • 整型比較方便,UUID比較需要先轉成ASCII在進行比較。

為什麼非主鍵索引結構葉子節點儲存的是主鍵值?(一致性和節省儲存空間)

  1. 減少了出現行移動或者資料頁分裂時二級索引的維護工作(當資料需要更新的時候,二級索引不需要修改,只需要修改聚簇索引,一個表只能有一個聚簇索引,其他的都是二級索引,這樣只需要修改聚簇索引就可以了,不需要重新構建二級索引);
  2. 聚簇索引也稱為主鍵索引,其索引樹的葉子節點中存的是整行資料,表中行的物理順序與鍵值的邏輯(索引)順序相同。一個表只能包含一個聚集索引。因為索引(目錄)只能按照一種方法進行排序;
  3. 非聚簇索引(普通索引)的葉子節點內容是主鍵的值。在 InnoDB 裡,非主鍵索引也被稱為二級索引(secondary index)。