關於索引我能說的那些事兒

来源:https://www.cnblogs.com/zhangweicheng/archive/2020/02/20/12336562.html
-Advertisement-
Play Games

本文是自己對MySQL的 索引的理解,如有錯誤,還望不吝指出。 1 索引 索引這兩個字著實有些太泛,而在我的理解中,其就是一個查字典的過程,比方說現在我們要從一本字典中查一個 字,那麼我們可以從目錄中的 字母找到這個 字,發現在 頁,然後翻到 就可以看到關於 這個的解釋、用法等。 可以看到我們不是從 ...


本文是自己對MySQL的InnoDB索引的理解,如有錯誤,還望不吝指出。

1 索引

  索引這兩個字著實有些太泛,而在我的理解中,其就是一個查字典的過程,比方說現在我們要從一本字典中查一個字,那麼我們可以從目錄中的n字母找到這個字,發現在164頁,然後翻到164頁就可以看到關於這個的解釋、用法等。

1581947228339

  可以看到我們不是從第一頁開始一頁一頁的找,而是先從目錄中根據拼音開頭找,找到之後翻到其對應的頁數就找到了我們所需要的牛字。在這整個過程中,這個目錄的字母就是我們所說的索引。我們查找數據的時候先通過這個目錄找到對應記錄的地址,再去這個地址找到我們所需要的數據,這個過程相比我們從頭找到尾的效率要高許多,這就是索引的作用——提高性能

​  接下來所講的內容如果沒有備註則都是以InnoDB為主。

2 索引的類型

​  在MySQLInnoDB引擎中,最常見的索引就是B-Tree索引和Hash索引。

  • B-Tree索引B-Tree索引是一種通用的叫法,在不同的儲存引擎中可能有不同的實現方式,而在InnoDB中則是用B+Tree來實現,跟普通的的B-Tree不同的是B+Tree只有在葉子節點才存放數據,在非葉子節點中只是存放一個Key值和節點的引用,具體關B+Tree索引我們放在下麵詳細講,普通的索引用的就是B+Tree。(以前會把B-Tree讀成B減樹,暴露了自己文盲的事實,正確的讀法應該是B樹,中間的-是杠不是減,B+Tree則是讀成B加樹)

  • Hash索引:意思就是講欄位的值經過一個Hash演算法之後得到一個Hash,再將這個HashK-V的形式寫入到一個Hash表中,key就是這個hash值,value則是我們所需要的數據鏈表,類似於JavaHashMap的實現,使用鏈表的原因是因為可能演算法的一些問題而導致哈希衝突的問題。這種索引是十分迅速的,相對於B+Tree依賴樹高度O(logN),其時間複雜度為O(1),既然如此迅速,為什麼InnoDB還是選擇B+Tree作為預設的索引類型?因為其雖然快速,但是還是有許多缺點:

    • 需要額外的列來儲存hash值:比方說我們在表中有url列,用普通索引滿足不了性能要求,我們可以使用hash索引,增加一個url_hash來儲存其hash值,那麼每次我們查詢的時候就會變成where url_hash = hash("www.baidu.com") and url = www.baidu.com,這樣的查詢效率可以有很大的提升,但是付出的代價是多一列的維護空間
    • 不能使用例如limit,order by等數據範圍操作:因為中間還要經過一個hash演算法,所以這種索引在這些方面的表現十分不理想,在這方面B+Tree的表現則十分的優異,平均性能來說還是B+Tree更高,況且對於平常的需求來說範圍數據的查詢要更多一些。

  這兩種算是比較常見的索引類型,除此之外還有一種全文索引,可以實現搜索引擎類似的功能,但還沒見人用過,便不加瞭解。

3 B+Tree的結構

​  首先先看看B+Tree是怎麼出現的。

​  在一開始的時候使用的是平衡二叉樹作為索引樹,但是隨著數據量的增大二叉樹的表現有點疲軟,後來便出現的一種新的結構叫作B-Tree,這種數據結構有多個子節點(不再是固定兩個),而在每個節點上面都存著數據和其他節點的引用,很大程度上解決了二叉樹帶來的效率問題,然而時間再次推進,B-Tree的表現也逐漸下滑,此時則出現了一種新的實現方式——B+Tree

​  關於B+Tree,我們先看一個圖。

1581951423270

​  如上圖,我們存儲的數據是1、2、3、4、5、6,所有的數據都在葉子節點中,所謂的葉子節點就是上圖中最下層真正存放數據的節點,而上面那些只存了key和引用的則稱之為非葉子節點。

​  這裡需要註意的是,在InnoDB中,只有主鍵索引的葉子節點存放才是真正的數據信息,其他列的索引在葉子節點中存放的數據信息是主鍵的值,也就是說如果我們使用的是普通的索引,那麼其查找的過程為:

​ 在使用的索引樹(有多少個索引就有多少棵樹)中進行查找,找到了對應的葉子節點之後拿到其儲存的主鍵值,再去主鍵索引樹中查找對應主鍵的葉子節點的數據信息,而一般把通過主鍵去磁碟中讀取數據的操作叫做'回'。

​  主鍵索引和普通索引可以結合下圖理解

1582011585220

​  這就是InnoDBB+Tree的實現方式,跟普通的B-Tree相比有了穩定的性能,並且在範圍查詢(比方說id<10)方面表現的更加優異。如上所說,B-Tree的結構直接把數據的信息放在節點中,沒有是否葉子節點之分,查到之後就立馬返回,如下:1582012558215

4 聚簇索引和非聚簇索引

​  聚簇索引並非一種索引類型而是一種儲存方式,表示索引的鍵值對和臨近的數據行儲存在一起,在物理的儲存順序是有序的,在InooDB中,主鍵索引就是聚簇索引的實現

​  由於數據行只有一顆索引樹有存,所以也就只有一個聚簇索引,也就是說除了主鍵索引是聚簇索引之外,其他列的索引都是非聚簇索引。而聚簇索引的儲存特性也就決定了我們在查到範圍數據比如limit 10這種操作的時候能夠進行順序IO而非隨機IO從而提升了查找的效率。

​  當然有優必有劣,聚簇索引的儲存方式也就決定了主鍵只有在遞增的時候發揮得比較好,主鍵是遞增的,每次插入時往上次插入位置的下一個位置插入就行(因為新增的主鍵一定比之前的大),如果頁滿了就插入下一頁,但是如果主鍵是不規則的,譬如UUID來做主鍵,由於其每次插入的主鍵不一定比之前的大,那麼則要進行比較從而進行數據的移動需要花費的時間和空間要更多一些,並且如果插入一個飽滿的頁中就會引發列分裂從而造成空間碎片

5 複合索引和覆蓋索引

​  首先我們得知道這兩個不是同一個概念。

  • 複合索引:表示在一個索引中使用到了多個列,這個索引在記憶體中的排序則是依照列在索引的順序來決定的。比方說複合索引idx_userId_sex_age('user_id', 'sex', 'age'),我們在使用where user_id = '1'的時候會到user_id的索引,使用where user_id = '1' and sex = '1'的時候會用到user_idsex兩個列的索引,也就是說只有當首碼列出現了再用此列索引才有效,如where sex = '1'或者where sex = '1' and age = '11'都不會用到索引idx_userId_sex_age因為當前的首碼列是user_id

  • 覆蓋索引:指的是當某個索引包含查詢所需要的所有欄位的時候,這個時候找到記錄之後則不再去主鍵樹中查找數據,而是直接返回索引包含的欄位只在記憶體操作而不進行IO,可以很大程度上提升效率,使用覆蓋索引的時候explain中的extra會出現using index,如下圖。1582033012553

  另外,使用覆蓋索引可以實現延遲關聯,從而提升查詢的效率(前提是使用覆蓋索引過濾的數據足夠多),比方說現在有一個SQL:

select * from user_info where user_number = '123' and user_name like '%三%';

  在user_info表中有複合索引(user_number, user_name),上面的寫法的執行過程為:

  1. 從索引樹中找到user_number='123'的所有主鍵(user_name為全模糊,不會用到索引),註意這裡還沒執行user_name like '%三%'的操作。
  2. 根據這些主鍵從主鍵索引中找到對應的數據行,將這些數據行從磁碟載入到記憶體中
  3. 載入完成之後,從這些數據行中篩選出user_name like '%三%'的數據,將這些數據返回

  這是正常的執行過程,但是我們可以改寫這個SQL,讓其變成使用覆蓋索引的形式:

SELECT
  *
FROM
  user_info
INNER JOIN (
  SELECT
      id
  FROM
      user_info
  WHERE
      user_number = '123'
  AND user_name LIKE '%三%'
) t ON user_info.id = t.id;

  這樣臨時表t則是使用覆蓋索引生成的記錄,是在記憶體操作,註意由於索引的葉子節點存儲的是主鍵值,所以使用主鍵值的話也能用到覆蓋索引

  這個寫法跟上面不同的地方在於,由於使用了覆蓋索引,所以對於user_numberuser_name的條件過濾都是在記憶體中進行的,在記憶體過濾完成之後將拿到的主鍵值再去主鍵索引取數據行。跟第一種寫法的效率區別則是在於覆蓋索引能夠過濾多少條數據

  拿這兩個SQL舉個例子,假設在user_info表中user_number='123'的數據有10W條,user_name中包含'三'的數據有200條,那麼如果是第一種寫法,則有:

從索引中拿到10Wuser_number='123'的主鍵值到主鍵索引中拿到10W條數據行然後載入到記憶體中,再從記憶體中的10W條數據中找出user_name包含'三'200條數據。

  而如果是第二種寫法,則變成了:

先在索引中找到user_number='123'的節點,然後再從這些節點中找出user_name包含'三'200個主鍵值,註意到目前為止都是記憶體操作還沒進行IO,然後根據這200個主鍵值從磁碟載入200條數據數據行到記憶體中返回。

  對比可以清楚的看到,第一種寫法進行了10W數據的IO再過濾,而使用覆蓋索引的方式則只進行200條數據的IO,性能的提升肯定是非常大的,這種使用覆蓋索引來提升性能的方式就叫做'延遲關聯'。當然,性能的提升決定於覆蓋索引能夠過濾的數據行數,如果上面的例子中user_name包含'三'的記錄有9W條,那麼此時'延遲關聯'的寫法提升就沒那麼明顯了。

6 Extra中的一些信息

​  最後講下MySQLexplainExtrausing whereusing indexusing index condition

  • using where表示使用到了除使用索引列外的條件進行過濾,需要註意的是如果使用的是複合索引,那

    麽條件中不是該複合索引的列的話則extra中會出現using where即便後面的條件也是一個索引(但在當前查詢中沒有使用到)。
    另外,using where不一定會進行回表,例如using where;using index同時出現的時候則表示,用到了覆蓋索引,並且where的條件中還有該覆蓋索引的其他列,但不是前導列此時會在覆蓋索引的返回數據上進行過濾,而不再訪問數據行,這種情況下不會進行回表。

  • using index表示用到了覆蓋索引。

  • using index condition:表示不使用到覆蓋索引的情況下,用到了複合索引中的其他非前導列作為查詢的條件。比方說複合索引為(user_id, name, age)SQL為:

     select * from user where user_id = 1 and age = 1 and sex = 1;

  此時由於age不是前導列,但為複合索引的其中一列,並且查詢的是所有列,並不會用到覆蓋索引,所以是index condition;using where而不是或者using index,其中using where是因為 sex = 1這個條件,如果沒有的話則只有using index condition
  註意:using index condition索引非前導列的條件(比方說上方的age)時,這部分的條件篩選是在記憶體中進行,而不是回表返回數據行之後再執行這個過濾條件。如上方的sql中,其順序就是先找到user_id = 1的索引記錄,然後在這些記錄中過濾出age = 1的記錄,到這裡都是記憶體操作再通過回表返回的數據行中過濾sex = 1的數據,所以using index condition的過濾時間是發生在回表之前
  
  
  
  
  
  

參考:《高性能MySQL》第三版


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、KeyBy 操作後,只有當 Key 的數量大於運算元的併發實例數才能獲得較好的計算性能。 A.而若Key 的數量比實例數量少,就會導致部分實例收不到數據,這些實例就得不到執行,這些實例的計算能力得不到充分發揮。 ~~B.當Key個數多餘並行實例數時,由於同一個 Key 對應的所有數據都能發送到同一 ...
  • 邏輯計劃 1. logicGraph或者jobGraph,其端點為operator,edge為數據流向。 2. operator往往代表一個函數。 3. 同一個分區內的具有連續上下游關係的函數組成operator chain,一個operator chain內的數據來流動過程中不會出現序列化和分區間 ...
  • 在早期版本的Spark中,shuffle過程沒有磁碟讀寫操作,是純記憶體操作,後來發現效率較低,且極易引發OOME,較新版本的Shuffle操作都加入了磁碟讀寫進行了改進。 1、未經優化的HashShuffleManager:上一個stage中每一個task會對下一個stage的每一個task寫一份數 ...
  • 1、Spark組件之間使用RPC機制進行通信。RPC的客戶端在本地編寫並調用業務介面,介面在本地通過RPC框架的動態代理機制生成一個對應的實現類,在這個實現類中完成soket通信、遠程調用等功能的邏輯包裝,而在RPC的服務端既編寫業務介面也編寫了具體的業務實現類,通過RPC框架以介面的方式暴露出來, ...
  • 1、spark的一大特性就是基於記憶體計算,Driver只保存任務的巨集觀性的元數據,數據量較小,且在執行過程中基本不變,不做重點分析,而真正的計算任務Task分佈在各個Executor中,其中的記憶體數據量大,且會隨著計算的進行會發生實時變化,所以Executor的記憶體管理才分析的重點。 2、在執行Sp ...
  • 1、Redis數據持久化的必要性 由於redis是基於記憶體的資料庫,面臨數據掉電易失的風險,要避免數據丟失,最好將記憶體數據持久化到磁碟等永久存儲介質上。服務重啟時,會先載入磁碟文件內的數據到記憶體,完成數據恢復。 2、RDB(RedisDB) 對記憶體中的redis全量數據進行 時點快照 並序列化,以文 ...
  • 1、常見的三種數據的集群存儲模式 1. full mirror:全量鏡像模式,單純備份模式,各個節點數據相同,都包含了全量數據,僅主節點可寫,保證了數據冗餘和讀的負載均衡。數據安全性高,橫向擴展能力差,資源利用率不高。 2. pure sharding:數據分片,每個節點的數據不相同,所有節點中數據 ...
  • 1、環境說明 | 操作系統 | CentOS Linux release 7.4.1708 (Core) | | | : : | | Ambari | 2.6.x | | HDP | 2.6.3.0 | | Spark | 2.x | | Phoenix | 4.10.0 HBase 1.2 | 2 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...