MySql 技術內幕 (第9章 索引)
9.1 緩衝池、順序讀取與隨機讀取
資料庫一般需要持久化,持久化就要和磁碟打交道,因此就會出現訪問磁碟的操作。隨機訪問磁碟是比較慢的,順序訪問會快很多。所以現在基本都有緩衝池的存在。
緩衝池作為記憶體和磁碟的中介軟體,主要是快取以頁的方式快取資料頁,查詢和修改時會先在快取檢視有沒有,有就命中,效率就很高了。或者沒有的話,先把資料頁調出到快取池中,再修改資料頁,然後非同步寫入磁碟持久化。
順序讀取: 順序地讀取磁碟的頁;
隨機讀取: 訪問的頁是不連續的,需要磁頭不斷移動;
9.2 資料結構與演算法
B+樹索引最為常見;
9.2.1 二分查詢法
B+樹索引只能找到某條記錄所在的頁,需要根據二分查詢法來進一步找到記錄所在頁的具體位置;
9.2.2 二叉查詢樹和平衡二叉樹
二叉查詢樹 中序遍歷得到值是排序的;
平衡二叉樹查詢效率高(任意節點兩棵子樹節點高度最大差為1);
9.3 B+樹
B+樹的精簡介紹: B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點,各個子節點通過指標進行連結。
9.3.1 B+樹的插入操作
對mysql的B+數的插入和刪除操作會引起分裂和合並的產生,主要是為了維護B+索引樹的平衡,不過有些時候可以通過左旋右旋來避免分裂合併。
B+樹保持平衡,插入新值後可能要做大量的拆分頁操作(磁碟操作), 為此B+樹提供了旋轉功能;
9.3.2 B+樹的刪除操作
使用填充因子控制樹的刪除變化,最小值是50%;
9.4 B+樹索引
索引是儲存引擎層面實現的,所以不同的儲存引擎底層的資料結構可能是不一樣的。
b+樹索引在資料中一個特點就是高扇出性,所以查詢速度是很快的。,
例如Innodb中,每個頁的大小為16KB;
資料庫中,B+樹高度一般都在2~4層, 這意味著查詢某一鍵值最多隻需要2-4次IO操作;
9.4.1 InnoDB B+樹索引
Innodb會自動建立一個6位元組的列作為主鍵;
B+樹索引分為:聚集索引和輔助索引。區別在於存放的資料內容。
聚集索引:根據主鍵建立的B+樹,聚集索引的葉子存放的是表中的真實資料。非葉子節點存放的是目錄項資料(頁號等)。
輔助索引:根據索引
9.4.2 MyISAM B+樹索引
所有行資料存放在MYD檔案中,
其B+樹索引都是輔助索引,存放於MYI檔案中;
primary key 索引唯一且不能為null,其索引頁的大小預設為1KB, 同樣不可以進行調整;
與innodb不同,因為沒有聚集索引,其索引葉節點存放的鍵值不是主鍵值,而是MYD檔案中的物理位置;
9.5 Cardinality
9.5.1 什麼是Cardinality
什麼時候需要新增B+樹索引,一般經驗是在訪問表很少部分行時使用才有意義。高選擇性的欄位才有意義。 比如性別,地區欄位,型別欄位,可取值範圍小,稱為低選擇性;
通過show index; 檢視Cardinality列的值來判斷是否要加對這一列加索引。
9.5.2 InnoDB儲存引擎怎樣統計Cardinality
Cardinality的統計是在儲存引擎層實現的。因為每個儲存引擎對B+樹的實現是不同的。
Cardinality的統計一般是通過取樣完成的。因為Cardinality的統計發生在insert和update操作,然而這兩個操作是很頻繁的,所以不會實時的更新的。
Innodb對於更新Cardinality資訊的策略為:
-
表中1/16的資料已發生變化
-
stat_modified_counter >2 000 000 000
stat_modified_counter 是Innodb內部的計數器,表示發生變化的次數;
取樣過程:
InnoDB儲存引擎每次隨機選擇8個葉節點進行取樣。所以每次檢視Cardinality值可能是不相同的。
-
取得b+樹索引中葉節點的數量,即為A
-
隨機取B+樹索引中的8個葉節點, 統計每個頁不同記錄的個數, 即P1 ,P2 ....P8
-
根據取樣資訊給出Cardinality預估值: Cardinality = (P1+P2+...+P8) * A / 8
9.6 B+樹索引的使用
9.6.1 不同應用中B+樹索引的使用
別盲目聽從,研究業務確定是否需要索引,對哪些列做索引。
9.6.2 聯合索引
聯合索引的鍵數量是多個,不是一個。
聯合索引本質上還是一顆B+樹;
比如聯合索引(a,b) ,
如果查詢條件 a=xxx and b=xxx 可以使用這個索引;
查詢條件 a= xxx 也可以使用這個索引;
查詢條件 b=xxx 不可以使用B+樹索引,因為葉子節點上,1,2,1,4,1,2 不是順序的;
聯合索引的另一個好處是可以對第二個鍵的排列(無需再對第二列做一次額外的排序操作),所以有時候可以提高查詢效率。
9.6.3 覆蓋索引
覆蓋索引:從輔助索引就可以等到查詢的記錄,而不需要回表操作。大量減少了IO操作。
一般來說,對於諸如(a,b)這類聯合索引,一般不可以選擇b列來進行查詢,但是在統計操作,如果是覆蓋索引,優化器會優先選擇。
9.6.4 優化器選擇不使用索引的情況
某些情況優化器並沒有選擇索引去查詢資料,而是全表掃描聚集索引,也就是全表掃描來得到資料, 這種情況多發生於範圍查詢,join操作;
原因: 選取的是整行資訊,而覆蓋索引是沒有包含全部資料資訊的,所以只能走全表掃描了。
如果確認使用輔助索引可以帶來更好的效能可以使用 FORCE INDEX 。
9.6.5 INDEX HINT
mysql支援INDEX HINT(顯式告訴優化選擇指定索引)。兩種情況需要:
-
mysql資料庫的優化器錯誤地選擇了某個索引,導致SQL語句執行得很慢;
-
某個SQL語句可以選擇的索引非常多,這時優化器選擇執行計劃時間的開銷可能會大於SQL語句本身;
DBA或者開發人員分析最優的索引選擇,通過INDEX HINT來強制優化器不進行各種執行路徑的成本分析,直接執行選擇指定的索引來完成查詢;
explain select * from t2 USE INDEX(a) where a=1 and b=2 ; explain select * from t2 FORCE INDEX(a) where a=1 and b=2 ;
-
USING INDEX 只是告訴優化器選擇指定的索引,優化器不一定真的會選擇。
-
FORCE INDEX 是強制優化器選擇指定的索引。
9.7 Multi-Range Read (MRR優化)
mysql5.6後才有的。
MRR優化的目的是: 減少磁碟的隨機訪問,並且將隨機訪問轉化為順序訪問。
MRR優化的好處:
-
使得資料訪問變為順序; 在查詢輔助索引時,先對得到的查詢結果按照主鍵進行排序,並按照主鍵排列的順序進行書籤查詢;
-
減少緩衝池頁被替換的次數 ;
-
批量處理對鍵值的查詢操作
對於InnoDB和MyISAM的範圍查詢和聯接查詢, MRR工作方式如下:
-
將查詢得到的輔助索引鍵值存放於一個快取中,這是快取中的資料是根據輔助索引鍵值排序;
-
將快取中的鍵值根據ROWID進行排序
-
根據rowId的排序順序來訪問實際的資料檔案
MRR引數 : optimizer_switch ;
鍵值緩衝區大小引數 : read_rnd_buffer_size ;
9.8 Index Condition Pushdown
mysql5.6後才有的。
ICP是一種根據索引來查詢的優化方式;
支援ICP後,mysql會取出索引同時,判斷是否可以進行where 條件過濾, 即 將where的部分過濾操作放在儲存引擎層; (推送的索引條件)
在某些查詢過程中,ICP 會達達減少上層SQL層對於記錄的索取(fetch),從而提高資料庫的整體效能;
優化器使用ICP時,server層將會把能夠通過使用索引進行評估的where條件下推到storage engine層
1) storage engine從索引中讀取下一條索引元組。
2) storage engine使用索引元組評估下推的索引條件。如果沒有滿足wehere條件,storage engine將會處理下一條索引元組(回到上一步)。只有當索引元組滿足下推的索引條件的時候,才會繼續去基表中讀取資料
3) 如果滿足下推的索引條件,storage engine通過索引元組定位基表的行和讀取整行資料並返回給server層。
9.9 T樹索引
9.9.1 T樹概述
從mysql5.1開始 NDB Cluster開始使用T樹。
T樹不將真實資料放在節點,只是存放資料的指標。
T樹節點由3個指標,一個有序陣列, 以及控制資訊組成;
3個指標分別指向父節點和左右子樹的指標 ;
有序陣列儲存的是資料指標,而非實際資料;
陣列中的第一個元素資料 稱為最小元素,最後一個數據稱為最大元素;
控制資訊中存放了關於該T樹節點的一些額外資訊;
在T樹中,含有左右子樹 的節點稱為內部節點;
沒有子樹的節點稱為 葉節點 ;
僅有一個子樹的 稱為 半葉節點 ;
每個內部節點存在一個相對應的葉節點或者半葉節點存放內部節點的最小值,稱為最大下界;
同時存在一個用於存放內部節點的最大值稱為,最小上界;
9.9.2 T樹的查詢、插入和刪除操作
T樹的查詢和二叉查詢樹類似,其查詢演算法為 :
-
從根節點root 開始查詢,比較節點中的最大值和最小值,如果查詢的值在邊界內,則利用二叉法查詢T樹 中的陣列;
-
若查詢的值比根節點的最小值小,則遞迴查詢左子樹
-
若查詢的值比根節點的最大值大,則遞迴查詢右子樹
-
若不存在該值,返回null
T樹的插入操作:
先通過查詢來定位邊界頁;
新插入的值被插入邊界頁後需要對T樹進行檢查是否平衡;
如果不平衡 ,則需要通過旋轉來保證T樹的平衡;
由於T樹的節點可以存放多個值,因此其旋轉的次數, 比平衡二叉樹又減少了很多;
T樹的插入演算法:
-
查詢邊界頁
-
如果查到邊界頁,則判斷是否有空間插入新的值, 如果有直接插入,插入操作完成;
如果沒有空間,則刪除該節點的最小值,插入新值;
接著查詢原插入值最大下界所在的節點,將刪除的值插入該節點,併成為新的最大下界值;
-
如果沒有查到邊界頁, 那麼在最後查詢的節點中嘗試插入記錄;
若該節點有空間可以插入,則直接插入 ,並且該記錄成為該節點的最小或最大記錄值 ;
若沒有空間插入,則分配一個葉節點進行插入;
-
若果分配了一個葉節點,則需要檢查T樹是否平衡 ;
如果不平衡則需要進行與平衡二叉樹同樣的旋轉操作, 以保證T樹重新回到平衡狀態, 然後此次操作完成;
T樹的刪除類似插入操作:
首先需要找到邊界頁,然後完成刪除操作,最後判斷是否需要旋轉來完成平衡操作;
-
查詢刪除記錄所在邊界頁, 如果查詢失敗,則報告錯誤停止操作;
-
如果刪除不會引起下溢位問題,則刪除該記錄後,節點中的記錄數> 所要求該節點的最小記錄數,那麼直接刪除記錄;
否則,如果節點是內部節點,那麼刪除該記錄,同時將該節點的最大下界值放回該內部節點中;
如果節點是葉節點或半葉節點,則直接刪除記錄(葉節點允許下溢位,半葉節點還需要進行下一步操作)
-
如果是半葉節點,則觀察半葉節點是否可以和葉節點進行合併,如果可以則強制將兩個節點合併為一個節點並刪除半葉節點
-
觀察刪除後的節點是否是空節點,若是 則刪除該節點
-
觀察T樹是否平衡,若不平衡則需要通過旋轉操作使其重新平衡;
9.9.3 T樹的旋轉
插入操作,只需要進行一次旋轉操作 , 對於刪除操作,可能需要進行多次的旋轉操作;
T樹有一種特殊的旋轉....
9.10 雜湊索引
InnoDB儲存引擎只支援自適應雜湊索引(不可以人工干預,對字典型別的查詢速度超快),
Memory儲存引擎支援雜湊索引。
雜湊函式是關鍵。
發生碰撞,解決辦法:
-
連結法
-
開放式向前探索法(1步跳,迭代n步跳)
檢視自適應雜湊索引的情況:
9.10.1 散列表
由直接定址表改進而來;
全域 U = {0, 1, ....m-1} 中存關鍵字;
用一個數組(即直接定址表) T [0....m-1] 表示動態集合, 其中每個位置(稱槽或桶) 對應全域U中一個關鍵字 ;
槽K指向 集合中一個關鍵字為K的元素,如果沒有則T[K]=NULL;
利用雜湊函式 h , 根據關鍵字K計算出槽的位置;
不過這樣有一個問題就是 兩個關鍵字可能對映到同一個槽上(碰撞) ;
資料庫中一般採用最簡單的解決方法,即連結法;
連結法中,將同一個槽中的所有元素放在一個連結串列中;
9.10.2 InnoDB儲存引擎中的雜湊演算法
Innodb使用雜湊演算法對字典進行查詢;
衝突機制採用連結串列方式;
雜湊函式採用除法雜湊方式;
對於緩衝池頁的雜湊來說,
緩衝池中一個page頁都有一個chain指標,指向相同雜湊函式值的頁;
比如當前引數innodb_buffer_pool_size 大小為10MB,則 共有 640x16KB 的頁, 對於緩衝池頁記憶體的散列表來說,則需要分配 640*2=1280 個槽,(略大於2倍緩衝頁數量的質數), 取比1280略大的質數1399, 則啟動時會分配1399個槽的散列表,用來雜湊查詢所在緩衝池中的頁;
將要查詢的頁轉換成自然數;
Innodb的表空間都有一個space號, 我們需要查詢的應該是某個表空間的某個聯絡的16Kb的頁, 即偏移量offset, Innodb將space左移20位,然後加上這個space和offset,
即 關鍵字 K=space <<20 +space +offset 然後通過除法雜湊到各個槽中去;
9.10.3 自適應雜湊索引
有innodb自己控制,不受DBA或開發人員控制; 不過可以通過引數開啟或禁用此特性;
檢視自適應雜湊索引的情況:
show engine innodb status ;
自適應雜湊索引經雜湊函式對映到一個散列表中, 對於 where index_col = 'xxx' 這種字典型別查詢非常快速;
雜湊索引只能是等值查詢。因為雜湊函式對映後就是一個值,就是通過比較值來得到對應的槽。
範圍查詢是不能使用自適應雜湊索引;
9.11 小結
其他
mysql -- show index from tablename 各列解釋
show index from table_name
這個命令有助於診斷效能低下的查詢,尤其是查詢是否使用了可用的索引。
下面介紹下 這個命令顯示的結果列的含義:
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
1.Table 表的名稱。
2.Non_unique 如果索引不能包括重複詞,則為0。如果可以,則為1。
3.Key_name 索引的名稱。
4.Seq_in_index 索引中的列序列號,從1開始。
5.Column_name 列名稱。
6.Collation 列以什麼方式儲存在索引中。在MySQL中,有值‘A’(升序)或NULL(無分類)。
7.Cardinality
索引中唯一值的數目的估計值。通過執行ANALYZE TABLE或myisamchk -a可以更新。基數根據被儲存為整數的統計資料來計數,所以即使對於小型表,該值也沒有必要是精確的。基數越大,當進行聯合時,MySQL使用該索引的機會就越大。
8.Sub_part 如果列只是被部分地編入索引,則為被編入索引的字元的數目。如果整列被編入索引,則為NULL。
9.Packed 指示關鍵字如何被壓縮。如果沒有被壓縮,則為NULL。
10.Null 如果列含有NULL,則含有YES。如果沒有,則該列含有NO。
11.Index_type 用過的索引方法(BTREE, FULLTEXT, HASH, RTREE)。
12.Comment 多種評註。
參考: https://blog.csdn.net/javamoo/article/details/70184088