mysql索引基本原理


前置概念:
  • 磁盤預讀:內存跟磁盤在發生數據交互的時候,一般情況下有一個最小的邏輯單元,即"頁"/"datapage",頁一般由操作系統決定是多大,一般是4k或8k。而進行數據交互的時候,可以取頁的整數倍來讀取。如innodb引擎每次讀取數據都是16k
  • B+樹
    • 每個關鍵字對應一棵子樹
    • 每個結點關鍵字個數n的范圍是[m/2]<=n<=m
    • 葉節點是包含信息的,其他所有非葉節點僅起到索引作用,包含了對應子樹的最大關鍵字和指向該子樹的指針
  • mysql底層構造
    • client層——>server層——>存儲引擎
      • client向server層發送SQL語句,server層通過連接器接收
      • server層的分析器對SQL語句進行語法分析,轉變為AST抽象語法樹
      • server層使用優化器對語句中具體查詢的數據
      • 執行器與存儲引擎進行IO交互,獲取查詢結果
 
從頭遍歷
沒有索引的查找方法:從第一頁記錄開始查,遍歷所有數據頁,相當耗費資源和時間。
 
簡單的索引方案——一層目錄
  1. 假設一個數據頁的數據已經存儲滿了,這時再添加一條記錄。如果該記錄的主鍵比現有記錄最大的主鍵小:首先必然會產生頁的分裂,其次,現有的最大主鍵用戶記錄會移到新的數據頁,而新插入的用戶記錄則插入到原來已滿的數據頁。頁的分裂始終保持——下一個數據頁中用戶主鍵值必須大於上一頁的用戶記錄主鍵值。
  2. 再假設上述頁分裂操作多次執行,導致數據頁數量非常多。此時應該給這些數據頁按照主鍵大小整合目錄頁。目錄頁只包含1.數據頁上最小的用戶主鍵值。2.數據頁的頁號。如此,只要將目錄頁放在連續的內存中,即可快速通過主鍵查找到用戶記錄
 
InnoDB的索引——B+樹
 
前置信息:由於面向的是真實的數據庫用戶,因此數據量有時會非常大。一個page只有16KB,別說用戶記錄了,就連目錄項都有可能裝不完。並且,如果數據頁做了增刪操作,那么所有目錄項也需要做調整,這樣非常浪費空間和資源。
 
  1. 因此,MySQL將數據頁和目錄頁做一個復用,由於目錄項和用戶記錄比較相似,因此讓他們使用同一個頁模版——0x45BF,即FIL_PAGE_INDEX。只使用一條屬性record_type將二者區分。除此之外,頁的結構、記錄分組(槽)等等都是一樣的(另外,page Header中信息自然時不一樣的)。
  2. 如果數據量實在太多,一張16KB全部裝着目錄項的page也不夠用,則產生頁分裂,使用多個目錄頁來裝;數據量巨大,連目錄頁都太多了,查找起來很慢,則會產生更高層次的目錄,將目錄頁存儲起來!這種結構,就稱之為B+樹。最頂端的節點是根節點,中間的節點為非葉子節點,而最底端的節點為葉子節點。所有的用戶記錄均被存儲在葉子節點中。
 
像這種層級往上疊的結構,每增加一個層級,所能容納的數據成幾何倍數增長,一個數據頁已經可以存放很多條用戶記錄了,一層數據頁則可以更多,一層目錄頁更是數量爆炸!
 
一般情況下,我們用到B+樹的層級不會超過4層,這樣只需查找3個目錄頁+1個數據頁+頁內二分查找,即可快速定位記錄。不要小看這4層的B+樹,4層B+ 樹已經可以存放千萬級數據了,如果還想放再多數據,只能擴容或者加表
 
 
聚簇索引,具有以下兩個特點的B+樹稱為聚簇索引:
  1. 使用記錄主鍵的大小進行記錄和頁的排序
    • 頁內記錄按住鍵大小排序形成單向鏈表結構,頁內划成分組,每組最大記錄的偏移量記錄在槽中,放在頁目錄中。
    • 數據頁按頁中用戶記錄的主鍵大小排序成雙向鏈表(用於對主鍵的范圍查找和分頁查找)
    • 目錄頁(存放目錄項的數據頁)分為不同層級,同層目錄頁根據頁中住鍵大小順序排列形成雙向鏈表
  2.  葉子節點存儲的是完整的用戶記錄,即不是二級索引,而是記錄中存儲了所有列的值(包括隱藏列)
 
只要我們的記錄擁有以下特點,那么我們就不需要手動顯式地使用INDEX創建索引,MYSQL會自動創建聚簇索引,在InnoDB中,聚簇索引就是數據的存儲方式,即:索引即數據,數據即索引。
 
而從存儲方面來說,索引和數據本身是存儲在磁盤的,查詢數據時會優先將索引加載在內存。索引存儲的信息就是存放目錄項的數據頁。在一個磁盤塊中,指針大小是固定的,因此當選擇某字段當索引時,應該盡量減小鍵值所占用的空間,這樣可以指數級地增加用於存放數據的空間
 
 
Mysql中的索引使用的是B+樹,而為什么不使用其他數據結構做呢?
  • hash:哈希沖突;范圍查詢時需逐個遍歷;對於內存空間要求高。mysql中也存在hash,即memory引擎使用的是hash索引,innodb支持自適應hash,即系統根據數據類型判斷使用哪種存儲結構
    • 二叉樹:無序
    • BST:二叉查找樹:插入時有序,左子樹小於子樹根節點,右子樹大於子樹根節點
      • 插入連續值時,可能退化成鏈表,時間復雜度提高
    • AVL:平衡二叉查找樹:有序,為了解決連續值退化的問題,通過左旋或右旋讓樹平衡,保持最短子樹和最長子樹高度不能超過1
      • 通過插入性能的損失來彌補查詢性能的提升
    • 紅黑樹:在旋轉平衡的基礎上,添加變色的行為。保持最長子樹不超過最短子樹的2倍即可。使得查詢性能和插入性能近似一致
  • 隨着數據的插入,樹的深度越來越深,意味着IO次數越多,影響數據讀取的效率,因此使用開始使用樹
    • B樹:多岔樹,有序,非葉子節點也是存儲着數據的,占用空間較大
    • B+樹:多岔樹,有序,只有葉子節點存儲着數據,非葉子節點存儲的是指針
    •  
 
索引的創建跟存儲引擎是掛鈎的,存儲引擎表示不同數據在磁盤的不同組織形式
 
在windows上的Mysql中,文件都是以不同后綴並成對的形式存在。數據結構用的都是B+樹,而存儲形式又有所不同
  • .frm:存放表結構
  • .MYD和.MYI:引擎是myisam,非聚簇索引。
    • .MYD:數據,Data
    • .MYI:索引,index
  • .ibd:存儲數據+索引,即引擎是innodb。只能有一個聚簇索引,可以有很多非聚簇索引。在插入數據時,必須要包含一個索引的key值。這個索引的key值可以是主鍵;若沒有主鍵也可以是唯一鍵;若唯一鍵也沒有,就會有一個自生成的6字節的rowid(對用戶不可見)
——聚簇索引與非聚簇索引——二者取決於數據和索引是否是放在一起的
——mysql表中有且至少有一個索引
 
 
 
 
索引覆蓋:若查詢結果包含普通索引列和聚簇索引列,則會直接返回,不需要從聚簇索引查詢任何數據,這就叫索引覆蓋
  • 注:面試常問——在設置主鍵時是否需要自增?答:在滿足業務需求的情況下,能自增盡量自增。因為自增是累加的,對前面的數據沒有影響,無需做插入操作,也就無需做磁盤塊/頁分裂的操作(即:頁分裂。當在一頁數據里插入數據時,如果數據滿了就需要分裂成多個頁或者磁盤塊來存儲,父親節點也可能需要分裂,而分裂會影響效率)。追加的效率較高
最左匹配:組合索引的匹配規則。若一張表里有兩列是組合索引,那么使用時必須先匹配左側的列,再匹配右側列
  • select * fro table where name=? and age=? 允許使用
  • select * fro table where name=?  允許使用
  • select * fro table where  age=? 無法使用
  • select * fro table where age=? and name=? 允許使用
  • 只要查詢條件是組合索引,那么就算順序相反,mysql內部的優化器會調整對應的順序
索引下推(mysql5.7后特性):使用組合索引時,直接根據組合索引在存儲引擎中獲取對應數據,無需在server層二次過濾(5.7之前),減少了server層於存儲引擎的交互,減少了IO量,提高了效率
 
 
 
 
 
 
 
 
聚簇索引的條件必須是以主鍵大小排序,即這個B+樹的查詢條件為主鍵。而如果以其他列作為查詢條件,我們需要換一個樹!重新新建一顆B+樹,這個樹采用其他列作為查詢條件,而它的葉子節點並不會存放完整的用戶記錄,只會存放該列所在記錄的主鍵。它的目錄項也不會使用主鍵+頁號,而是使用其他列+頁號搭配。
 
二級索引的查詢過程:
由於其他列可能沒有唯一約束,所以匹配到的可能有多個主鍵(記錄)。因此當查詢到第一條記錄后,我們通過查詢到的主鍵到聚簇索引中查找完整的用戶記錄。這個過程(通過攜帶主鍵回到聚簇索引里查詢完整的用戶記錄的過程)就叫回表。然后再返回到剛才的B+樹,回到剛才定位到的那條用戶記錄,順着記錄所形成的單向鏈表找到其他符合條件的記錄,然后再回表,再繼續找…… 
 
回表的定義:根據普通索引查詢到聚簇索引的key值之后,再根據key值在聚簇索引中獲取所有行記錄。回表走了2次B+樹
 
 
由於這種以非主鍵列的大小為排序規則而建立的樹需要執行回表才能查到完整用戶記錄,所以這種B+樹也稱為二級索引,這顆B+樹是為其他列所建立的索引,該列是索引列。索引列記錄的不是完整的用戶信息。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
數據存儲在磁盤,一般情況況下查詢速率慢,都是卡在IO上。因此需要在滿足需求的前提下提高IO效率:
  • 減少IO次數
  • 減少IO量
 
 
profiling——mysql自帶的性能分析工具(單條語句)
  • 開啟profiling:set profiling=1;
  • 執行SQL語句,然后show profiles; 即可查出上條語句執行的實際時間
  • 想要查詢profiles結果表中具體某條語句的所有狀態耗費時長,或者想查詢某條語句的某個狀態的使用情況,根據可使用profiles表語句對應的Query_ID來查詢,語法如下:show profile for [type] query [id]。但這種查詢方式在未來版本可能會被其他工具所取代
 
進階--Myysql性能模式:
5.7默認開啟性能模式,在初始數據庫中,會有一個performance_schema庫,里面有眾多相關表,而通過show variables like 'performance_schema';查詢性能模式的開啟狀態。而修改狀態無法直接update,需要更改mysql配置文件my.cnf
 
 


免責聲明!

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



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