1. 程式人生 > >MySQL優化(三):索引原理及索引優化

MySQL優化(三):索引原理及索引優化

建立高效能索引

索引是提高MySQL查詢效能的一個重要途徑,但過多的索引可能會導致過高的磁碟使用率以及過高的記憶體佔用,從而影響應用程式的整體效能。應當儘量避免事後才想起新增索引,因為事後可能需要監控大量的SQL才能定位到問題所在,而且新增索引的時間肯定是遠大於初始新增索引所需要的時間,可見索引的新增也是非常有技術含量的。

接下來將向你展示一系列建立高效能索引的策略,以及每條策略其背後的工作原理。但在此之前,先了解與索引相關的一些演算法和資料結構,將有助於更好的理解後文的內容。

索引相關的資料結構和演算法

通常我們所說的索引是指B-Tree索引,它是目前關係型資料庫中查詢資料最為常用和有效的索引,大多數儲存引擎都支援這種索引。使用

B-Tree這個術語,是因為MySQL在CREATE TABLE或其它語句中使用了這個關鍵字,但實際上不同的儲存引擎可能使用不同的資料結構,比如InnoDB就是使用的B+Tree

B+Tree中的B是指balance,意為平衡。需要注意的是,B+樹索引並不能找到一個給定鍵值的具體行,它找到的只是被查詢資料行所在的頁,接著資料庫會把頁讀入到記憶體,再在記憶體中進行查詢,最後得到要查詢的資料。

在介紹B+Tree前,先了解一下二叉查詢樹,它是一種經典的資料結構,其左子樹的值總是小於根的值,右子樹的值總是大於根的值,如下圖①。如果要在這課樹中查詢值為5的記錄,其大致流程:先找到根,其值為6,大於5,所以查詢左子樹,找到3,而5大於3,接著找3的右子樹,總共找了3次。同樣的方法,如果查詢值為8的記錄,也需要查詢3次。所以二叉查詢樹的平均查詢次數為(3 + 3 + 3 + 2 + 2 + 1) / 6 = 2.3次,而順序查詢的話,查詢值為2的記錄,僅需要1次,但查詢值為8的記錄則需要6次,所以順序查詢的平均查詢次數為:(1 + 2 + 3 + 4 + 5 + 6) / 6 = 3.3次,因此大多數情況下二叉查詢樹的平均查詢速度比順序查詢要快。

二叉查詢樹和平衡二叉樹

由於二叉查詢樹可以任意構造,同樣的值,可以構造出如圖②的二叉查詢樹,顯然這棵二叉樹的查詢效率和順序查詢差不多。若想二叉查詢數的查詢效能最高,需要這棵二叉查詢樹是平衡的,也即平衡二叉樹(AVL樹)。

平衡二叉樹首先需要符合二叉查詢樹的定義,其次必須滿足任何節點的兩個子樹的高度差不能大於1。顯然圖②不滿足平衡二叉樹的定義,而圖①是一課平衡二叉樹。平衡二叉樹的查詢效能是比較高的(效能最好的是最優二叉樹),查詢效能越好,維護的成本就越大。比如圖①的平衡二叉樹,當用戶需要插入一個新的值9的節點時,就需要做出如下變動。

平衡二叉樹旋轉

通過一次左旋(注:什麼是二叉樹的左旋和右旋?可以參考二叉樹的左旋和右旋

)操作就將插入後的樹重新變為平衡二叉樹是最簡單的情況了,實際應用場景中可能需要旋轉多次。至此我們可以考慮一個問題,平衡二叉樹的查詢效率還不錯,實現也非常簡單,相應的維護成本還能接受,為什麼MySQL索引不直接使用平衡二叉樹?

隨著資料庫中資料的增加,索引本身大小隨之增加,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存的磁碟上。這樣的話,索引查詢過程中就要產生磁碟I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級。可以想象一下一棵幾百萬節點的二叉樹的深度是多少?如果將這麼大深度的一顆二叉樹放磁碟上,每讀取一個節點,需要一次磁碟的I/O讀取,整個查詢的耗時顯然是不能夠接受的。那麼如何減少查詢過程中的I/O存取次數?

一種行之有效的解決方法是減少樹的深度,將二叉樹變為m叉樹(多路搜尋樹),而B+Tree就是一種多路搜尋樹。理解B+Tree時,只需要理解其最重要的兩個特徵即可:第一,所有的關鍵字(可以理解為資料)都儲存在葉子節點(Leaf Page),非葉子節點(Index Page)並不儲存真正的資料,所有記錄節點都是按鍵值大小順序存放在同一層葉子節點上。其次,所有的葉子節點由指標連線。如下圖為高度為2的簡化了的B+Tree

簡化B+Tree

怎麼理解這兩個特徵?MySQL將每個節點的大小設定為一個頁的整數倍(原因下文會介紹),也就是在節點空間大小一定的情況下,每個節點可以儲存更多的內結點,這樣每個結點能索引的範圍更大更精確。所有的葉子節點使用指標連結的好處是可以進行區間訪問,比如上圖中,如果查詢大於20而小於30的記錄,只需要找到節點20,就可以遍歷指標依次找到25、30。如果沒有連結指標的話,就無法進行區間查詢。這也是MySQL使用B+Tree作為索引儲存結構的重要原因。

MySQL為何將節點大小設定為頁的整數倍,這就需要理解磁碟的儲存原理。磁碟本身存取就比主存慢很多,在加上機械運動損耗(特別是普通的機械硬碟),磁碟的存取速度往往是主存的幾百萬分之一,為了儘量減少磁碟I/O,磁碟往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個位元組,磁碟也會從這個位置開始,順序向後讀取一定長度的資料放入記憶體,預讀的長度一般為頁的整數倍。

頁是計算機管理儲存器的邏輯塊,硬體及OS往往將主存和磁碟儲存區分割為連續的大小相等的塊,每個儲存塊稱為一頁(許多OS中,頁的大小通常為4K)。主存和磁碟以頁為單位交換資料。當程式要讀取的資料不在主存中時,會觸發一個缺頁異常,此時系統會向磁碟發出讀盤訊號,磁碟會找到資料的起始位置並向後連續讀取一頁或幾頁載入記憶體中,然後一起返回,程式繼續執行。

MySQL巧妙利用了磁碟預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也儲存在一個頁裡,加之計算機儲存分配都是按頁對齊的,就實現了讀取一個節點只需一次I/O。假設B+Tree的高度為h,一次檢索最多需要h-1次I/O(根節點常駐記憶體),複雜度O(h) = O(logmN)。實際應用場景中,M通常較大,常常超過100,因此樹的高度一般都比較小,通常不超過3。

最後簡單瞭解下B+Tree節點的操作,在整體上對索引的維護有一個大概的瞭解,雖然索引可以大大提高查詢效率,但維護索引仍要花費很大的代價,因此合理的建立索引也就尤為重要。

仍以上面的樹為例,我們假設每個節點只能儲存4個內節點。首先要插入第一個節點28,如下圖所示。

leaf page和index page都沒有滿

接著插入下一個節點70,在Index Page中查詢後得知應該插入到50 - 70之間的葉子節點,但葉子節點已滿,這時候就需要進行也分裂的操作,當前的葉子節點起點為50,所以根據中間值來拆分葉子節點,如下圖所示。

Leaf Page拆分

最後插入一個節點95,這時候Index Page和Leaf Page都滿了,就需要做兩次拆分,如下圖所示。

Leaf Page與Index Page拆分

拆分後最終形成了這樣一顆樹。

最終樹

B+Tree為了保持平衡,對於新插入的值需要做大量的拆分頁操作,而頁的拆分需要I/O操作,為了儘可能的減少頁的拆分操作,B+Tree也提供了類似於平衡二叉樹的旋轉功能。當Leaf Page已滿但其左右兄弟節點沒有滿的情況下,B+Tree並不急於去做拆分操作,而是將記錄移到當前所在頁的兄弟節點上。通常情況下,左兄弟會被先檢查用來做旋轉操作。就比如上面第二個示例,當插入70的時候,並不會去做頁拆分,而是左旋操作。

左旋操作

通過旋轉操作可以最大限度的減少頁分裂,從而減少索引維護過程中的磁碟的I/O操作,也提高索引維護效率。需要注意的是,刪除節點跟插入節點類似,仍然需要旋轉和拆分操作,這裡就不再說明。

高效能策略

通過上文,相信你對B+Tree的資料結構已經有了大致的瞭解,但MySQL中索引是如何組織資料的儲存呢?以一個簡單的示例來說明,假如有如下資料表:

對於表中每一行資料,索引中包含了last_name、first_name、dob列的值,下圖展示了索引是如何組織資料儲存的。

索引如何組織資料儲存,來自:高效能MySQL

可以看到,索引首先根據第一個欄位來排列順序,當名字相同時,則根據第三個欄位,即出生日期來排序,正是因為這個原因,才有了索引的“最左原則”。

1、MySQL不會使用索引的情況:非獨立的列

“獨立的列”是指索引列不能是表示式的一部分,也不能是函式的引數。比如:

我們很容易看出其等價於 id = 4,但是MySQL無法自動解析這個表示式,使用函式是同樣的道理。

2、字首索引

如果列很長,通常可以索引開始的部分字元,這樣可以有效節約索引空間,從而提高索引效率。

3、多列索引和索引順序

在多數情況下,在多個列上建立獨立的索引並不能提高查詢效能。理由非常簡單,MySQL不知道選擇哪個索引的查詢效率更好,所以在老版本,比如MySQL5.0之前就會隨便選擇一個列的索引,而新的版本會採用合併索引的策略。舉個簡單的例子,在一張電影演員表中,在actor_id和film_id兩個列上都建立了獨立的索引,然後有如下查詢:

老版本的MySQL會隨機選擇一個索引,但新版本做如下的優化:

  • 當出現多個索引做相交操作時(多個AND條件),通常來說一個包含所有相關列的索引要優於多個獨立索引。

  • 當出現多個索引做聯合操作時(多個OR條件),對結果集的合併、排序等操作需要耗費大量的CPU和記憶體資源,特別是當其中的某些索引的選擇性不高,需要返回合併大量資料時,查詢成本更高。所以這種情況下還不如走全表掃描。

因此explain時如果發現有索引合併(Extra欄位出現Using union),應該好好檢查一下查詢和表結構是不是已經是最優的,如果查詢和表都沒有問題,那隻能說明索引建的非常糟糕,應當慎重考慮索引是否合適,有可能一個包含所有相關列的多列索引更適合。

前面我們提到過索引如何組織資料儲存的,從圖中可以看到多列索引時,索引的順序對於查詢是至關重要的,很明顯應該把選擇性更高的欄位放到索引的前面,這樣通過第一個欄位就可以過濾掉大多數不符合條件的資料。

索引選擇性是指不重複的索引值和資料表的總記錄數的比值,選擇性越高查詢效率越高,因為選擇性越高的索引可以讓MySQL在查詢時過濾掉更多的行。唯一索引的選擇性是1,這是最好的索引選擇性,效能也是最好的。

理解索引選擇性的概念後,就不難確定哪個欄位的選擇性較高了,查一下就知道了,比如:

是應該建立(staff_id,customer_id)的索引還是應該顛倒一下順序?執行下面的查詢,哪個欄位的選擇性更接近1就把哪個欄位索引前面就好。

多數情況下使用這個原則沒有任何問題,但仍然注意你的資料中是否存在一些特殊情況。舉個簡單的例子,比如要查詢某個使用者組下有過交易的使用者資訊:

MySQL為這個查詢選擇了索引(user_group_id,trade_amount),如果不考慮特殊情況,這看起來沒有任何問題,但實際情況是這張表的大多數資料都是從老系統中遷移過來的,由於新老系統的資料不相容,所以就給老系統遷移過來的資料賦予了一個預設的使用者組。這種情況下,通過索引掃描的行數跟全表掃描基本沒什麼區別,索引也就起不到任何作用。

推廣開來說,經驗法則和推論在多數情況下是有用的,可以指導我們開發和設計,但實際情況往往會更復雜,實際業務場景下的某些特殊情況可能會摧毀你的整個設計。

4、避免多個範圍條件

實際開發中,我們會經常使用多個範圍條件,比如想查詢某個時間段內登入過的使用者:

這個查詢有一個問題:它有兩個範圍條件,login_time列和age列,MySQL可以使用login_time列的索引或者age列的索引,但無法同時使用它們。

5、覆蓋索引

如果一個索引包含或者說覆蓋所有需要查詢的欄位的值,那麼就沒有必要再回表查詢,這就稱為覆蓋索引。覆蓋索引是非常有用的工具,可以極大的提高效能,因為查詢只需要掃描索引會帶來許多好處:

  • 索引條目遠小於資料行大小,如果只讀取索引,極大減少資料訪問量

  • 索引是有按照列值順序儲存的,對於I/O密集型的範圍查詢要比隨機從磁碟讀取每一行資料的IO要少的多

6、使用索引掃描來排序

MySQL有兩種方式可以生產有序的結果集,其一是對結果集進行排序的操作,其二是按照索引順序掃描得出的結果自然是有序的。如果explain的結果中type列的值為index表示使用了索引掃描來做排序。

掃描索引本身很快,因為只需要從一條索引記錄移動到相鄰的下一條記錄。但如果索引本身不能覆蓋所有需要查詢的列,那麼就不得不每掃描一條索引記錄就回表查詢一次對應的行。這個讀取操作基本上是隨機I/O,因此按照索引順序讀取資料的速度通常要比順序地全表掃描要慢。

在設計索引時,如果一個索引既能夠滿足排序,又滿足查詢,是最好的。

只有當索引的列順序和ORDER BY子句的順序完全一致,並且所有列的排序方向也一樣時,才能夠使用索引來對結果做排序。如果查詢需要關聯多張表,則只有ORDER BY子句引用的欄位全部為第一張表時,才能使用索引做排序。ORDER BY子句和查詢的限制是一樣的,都要滿足最左字首的要求(有一種情況例外,就是最左的列被指定為常數,下面是一個簡單的示例),其他情況下都需要執行排序操作,而無法利用索引排序。

// 最左列為常數,索引:(date,staff_id,customer_id)

select  staff_id,customer_id from demo where date = '2015-06-01' order by staff_id,customer_id

7、冗餘和重複索引

冗餘索引是指在相同的列上按照相同的順序建立的相同型別的索引,應當儘量避免這種索引,發現後立即刪除。比如有一個索引(A,B),再建立索引(A)就是冗餘索引。冗餘索引經常發生在為表新增新索引時,比如有人新建了索引

相關推薦

MySQL優化索引原理索引優化

建立高效能索引索引是提高MySQL查詢效能的一個重要途徑,但過多的索引可能會導致過高的磁碟使用率以及過高的記憶體佔用,從而影響應用程式的整體效能。應當儘量避免事後才想起新增索引,因為事後可能需要監控大量的SQL才能定位到問題所在,而且新增索引的時間肯定是遠大於初始新增索引所需

並不對勁的圖論專題SPFA算法的優化

a算法 bubuko 等於 dfs size iomanip 最小 bre else if 1.bzoj1489-> 這是個新套路。 我們希望找到最小的x,那麽可以二分x,然後判斷是否存在圈的邊權的平均值小於等於x。 設圈的邊權依次為w1,w2,w3,…,wk,平均值

ElasticSearchJava操作ElasticSearch索引之CRUD

transport delete end std testin python網絡 search ava socket 1 package com.gxy.ESChap01; 2 3 import java.net.InetAddress; 4 impor

JVM效能優化垃圾收集

原文地址,譯文地址,譯者:Greenster Java平臺的垃圾收集機制顯著提高了開發者的效率,但是一個實現糟糕的垃圾收集器可能過多地消耗應用程式的資源。在Java虛擬機器效能優化系列的第三部分,Eva Andreasson向Java初學者介紹了Java平臺的記憶體模型和垃圾收集機制。她解釋了

JAVA通過JDBC操作MySQL資料庫PreparedStatement介面操作資料庫

JAVA通過JDBC操作MySQL資料庫(三):PreparedStatement介面操作資料庫 Statement介面的問題 PreparedStatement介面操作資料庫 Statement介面的問題 在文章JAVA通過JDBC操作

Docker容器虛擬化映象原理與生成新映象

一、映象原理         映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,他包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。 UnionFS(聯合檔案系統):Union檔案系統(UnionFS)是一種

Apache Flink 進階Checkpoint 原理解析與應用實踐

大家好,今天我將跟大家分享一下 Flink 裡面的 Checkpoint,共分為四個部分。首先講一下 Checkpoint 與 s

前端性能優化移動端瀏覽器前端優化策略

因此 本地 網絡流量 桌面 cse kit 極致 加載 文件 相對於桌面端瀏覽器,移動端Web瀏覽器上有一些較為明顯的特點:設備屏幕較小、新特性兼容性較好、支持一些較新的HTML5和CSS3特性、需要與Native應用交互等。但移動端瀏覽器可用的CPU計算資源和網絡資源極為

併發程式設計—— ReentrantLock實現原理原始碼分析

  ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行為語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高階的擴充套件功能,比如它可以實現公平鎖,同時也可以

Numpy學習筆記建立矩陣運算

矩陣的變換 #矩陣的形狀變換 改變矩陣的形狀 print(np.arange(15)) a=np.arange(15).reshape(3,5) a a.shape a.ndim#矩陣的維度 a.dtype.name a.size 運算結果: [ 0 1

初識TclTcl 變數運算子

目錄 Tcl變數 變數命名 動態型別 數學表示式 TCL運算子 算術運算子 關係運算符 邏輯運算子 位運算子 三元運算子 Tcl運算子優先順序 Tcl算術運算子 例子 Tcl關係運算符 例子 Tcl邏輯運算子 示例 Tcl位運算

人臉識別之人臉檢測--Haar特徵原理實現

本文主要由於OpenCV的haartraining程式,對haar特徵的補充及程式碼註釋。 Haar特徵的原理是什麼? Haar特徵分為三類:邊緣特徵、線性特徵、中心特徵和對角線特徵,組合成特徵模板。特徵模板內有白色和黑色兩種矩形,並定義該模板的特徵值為白色矩形畫

圖文並茂- 12C RAC的安裝 Oracle 安裝建庫

接前面的,接下來安裝Oracle 軟體,並建庫。 下圖中,自檢的一些問題,可以忽略,主要原因為swap設定較小,生產環境建議按照官方文件設定swap,中間兩項,主要原因為使用了/etc/hosts,而不是使用了DNS。第一項第四

MySQL的JOINJOIN優化實踐之內循環的次數

ted www str 連接 pri 記錄 font 語句 style 這篇博文講述如何優化內循環的次數。內循環的次數受驅動表的記錄數所影響,驅動表記錄數越多,內循環就越多,連接效率就越低下,所以盡量用小表驅動大表。先插入測試數據。 CREATE TABLE t1

MySQL效能管理架構設計SQL查詢優化、分庫分表 - 完結篇

一、SQL查詢優化(重要) 1.1 獲取有效能問題SQL的三種方式 通過使用者反饋獲取存在效能問題的SQL; 通過慢查日誌獲取存在效能問題的SQL; 實時獲取存在效能問題的SQL; 1.1.2 慢查日誌分析工具 相關配置引數: slow

Mysql 數據庫優化——分區和分表【個人經驗】

incr 返回 for 16px 使用 tree 主鍵 ref 相同   引:MyISAM存儲引擎的表在數據庫中,每一個表都被存放為三個以表名命名的物理文件。     1、首先肯定會有任何存儲引擎都不可缺少的存放表結構定義信息的.frm文件,     2、另外還有.MYD和

Redux和React-Redux的實現中間件的原理和applyMiddleware、Thunk的實現

調用 map 介紹 typeof 觀察者 ets 返回 async 基本原理 現在我們的Redux和React-Redux已經基本實現了,在Redux中,觸發一個action,reducer立即就能算出相應的state,如果我要過一會才讓reducer計算state呢怎麽辦

深入理解MyBatis的原理配置文件上

dynamic 如何 turn ready conf 屬性。 支持 left bool 前言:前文提到一個入門的demo,從這裏開始,會了解深入 MyBatis 的配置,本文講解 MyBatis 的配置文件的用法。 目錄 1、properties 元素 2、設置(set

深入理解MyBatis的原理配置文件用法

pac amt 單個 gis obb rri tab obj 用戶 前言:前文講解了 MyBatis 的配置文件一部分用法,本文將繼續講解 MyBatis 的配置文件的用法。 目錄 1、typeHandler 類型處理器 2、ObjectFactory 3、插件 4、e

Java併發synchronized實現原理

一、synchronized用法 Java中的同步塊用synchronized標記。 同步塊在Java中是同步在某個物件上(監視器物件)。 所有同步在一個物件上的同步塊在同時只能被一個執行緒進入並執行操作。 所有其他等待進入該同步塊的執行緒將被阻塞,直到執行該同步塊中的執行緒退出。 (注:不要使用全