Mysql索引是什麼「鬼」

前言

索引有不少種,hash索引,B樹索引,B+樹索引,全文索引等。Mysql支持多種存儲引擎,多種存儲引擎對索引的支持也各不相同。本文探究Mysql爲何使用B+樹來做爲索引的數據結構,索引的原理已經Sql中索引的優化。
Mysql官方對索引的定義是:索引(Index)是幫助Mysql高效獲取數據的數據結構。提取句子主幹就是:索引是數據結構。mysql

索引的原理

索引的目的

索引的目的在於提升查詢或檢索效率。例如咱們要在字典中查詢「mysql」這個單詞,是否是先要查詢m開頭的單詞表,而後在查詢第二個字母爲y的單詞,而後縮小範圍繼續找,知道找到「mysql」這個單詞爲止或者查無此詞。這就好像咱們沿着一個樹從樹根開始找,沿着主幹,樹幹,到最後的末梢,走了其中的一條路徑。這比一個查詢一個鏈表的結構,從頭找到尾,在大多數狀況下,效率要高得多。算法

Mysql的索引爲何是B+樹

爲何不用普通的二叉樹,這裏就沒必要多說了,由於對於大的數據量,二叉樹的高度過高,索引的效率低下。這裏主要說明爲何不用B樹(B-樹就是B樹),而是用B+樹。sql

B樹(B-樹)介紹

咱們都知道二叉樹查詢的時間複雜度爲O(logN),查詢效率已經夠高了,但爲何還要有B樹和B+樹呢?答案是磁盤IO。咱們都知道,IO操做的效率很低,當有存儲的有很大的數據量,查詢的時候,咱們不可能把所有數據都加載到內存中,只能逐一加載磁盤頁,每一個磁盤頁對應樹的節點,形成大量的磁盤IO操做(最壞狀況下,磁盤IO操做次數是樹的高度),平衡二叉樹因爲樹的高度太大形成磁盤IO讀寫過於頻繁,從而致使效率低下,因此多路查找樹-B樹/B+樹應運而生。
下面是一個三階的B樹(實際中節點元素不少)數據庫

B樹有如下特色:

  • 在一個節點中存放着數據和指針,且相互間隔
  • 在同一個節點中,key是增序的
  • 若是一個節點最左邊的指針不爲空,則它指定的節點左右的key小於最左邊的key。中間的指針指向的節點的key位於相鄰兩個key的中間。
  • B樹中不一樣節點存放的key和指針可能數量不一致,可是每一個節點的域和上限是一致的,因此在實現中B樹每每對每一個節點申請同等大小的空間
  • 每一個非葉子節點由n-1個key和n個指針組成,其中d<=n<=2d

B+樹

B+樹有如下特色:緩存

  • 內節點不存儲data,只存儲key和指針,葉子結點不存儲指針,只存儲key和data
  • 內節點和葉子結點的大小不一樣,由於存儲的東西不一樣
  • 每一個非葉子結點的指針上限爲2d而不是2d+1
  • 由於節點內部沒有data,因此有更多的空間放key,因此B+樹的出度通常比B樹要大,而對於必定的數據,出度大的話,樹的深度就小,因此B+樹的檢索效率比B樹高

爲何B+樹比B樹更適合Mysql索引

  • B+樹的磁盤讀取代價低:由於B+樹的非葉子結點沒有存儲數據,因此若是把全部同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀內存中的須要查找的關鍵字也就越多。相對來講IO讀寫次數也就下降了。
  • B+樹的查詢效率更穩定:因爲B+樹的分支結點並非最終指向文件內容的結點,只是葉子結點的索引,因此任意關鍵字的查找都必須從根節點走向分支結點,查詢路徑相同。但B樹的分支結點保存有數據,因此查詢路徑可能不一樣。
  • B+樹便於執行掃庫操做:因爲B+樹的數據都存儲在葉子節點上,分支節點均爲索引,方便掃庫,只需掃一遍葉子便可。可是B樹在分支節點上都保存着數據,要找到具體的順序數據,須要執行一次中序遍從來查找

Mysql的索引實現

咱們知道Mysql有兩種經常使用的存儲引擎,MyISAM和InnoDB,這兩種存儲引擎對索引的實現方式是不一樣的。安全

MyISAM索引實現

MyISAM使用B+樹做爲索引的結構,葉子結點的data域存放的是數據記錄的地址。 bash

上圖中是以Col1做爲主鍵的MyISAM主索引的示意圖。能夠看到,組下面一層葉子結點的data域存放的是數據記錄的地址。若是咱們在字段Col2上建一個輔助索引,那麼索引的結構以下:

MyISAM索引檢索算法是這樣的,首先按照B+樹的搜索算法查詢索引,若是指定的key存在,則取出data域的值,而後用data域的地址查詢數據記錄。MyISAM的索引方式也叫「非彙集的」,跟InnoDB的「彙集索引」相區分,由於數據記錄和索引不在一塊兒。

InnoDB索引實現

InnoDB的索引實現方式與MyISAM的索引實現方式的區別有兩個:
第一,InnoDB的數據文件自己就是索引文件。在InnoDB中,數據文件自己就是按B+樹組織的一個索引結構,並且是主索引結構。數據和索引在一塊兒,葉子結點保存了完整的數據記錄,這種索引叫作彙集索引。由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。服務器

第二,InnoDB輔助索引的data域存儲的是相應記錄主鍵的值而不是地址。如圖,下圖是定義在Col3上的一個輔助索引的示意圖。葉子結點存儲了col3的值和對應的主鍵col1的值。

索引優化

牆裂建議使用自增主鍵

在使用InnoDB做爲存儲引擎時,若是沒有特殊須要,請永遠是用一個與業務無關的自增字段做爲主鍵,並且這個字段長度不宜過大。爲何?InnoDB使用匯集索引,數據記錄自己存放在主索引(B+樹)的葉子結點上,這就要求同一個葉子結點(大小爲一個內存頁或磁盤頁)的數據記錄按主鍵順序存放,每當一條新的記錄插入時,mysql會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)。若是使用自增主鍵,那麼每次插入新的記錄,記錄就會順序插入到當前節點的下一個位置。這樣就會造成一個緊湊的索引結構,每次插入不須要移動已有數據,所以效率很高。以下圖:網絡

若是使用非自增主鍵(例如身份證號或學號這種無序字符串),每次插入主鍵近似隨機,每次記錄都要插入到現有索引頁的中間的某個位置,這時不得不移動元素來完成插入,增長了開銷。以下圖:數據結構

索引的最左前綴原則

聯合索引:mysql能夠將多個列按照順序做爲一個索引,這種索引叫作聯合索引。
索引的最左匹配原則是:假如索引列分別爲A,B,C,順序也是A,B,C,那麼:

  • 查詢的時候,若是查詢【A】,【A,B】,【A,B,C】,可使用索引查詢。
  • 若是查詢的時候,查詢【A,C】,因爲中間缺失了B,那麼C這個索引是用不到的,只能用到A索引。
  • 若是查詢的時候,查詢【B】,【B,C】或【C】,因爲缺失了最左前綴A,那麼是用不到這個聯合索引的,除非有其餘索引。
  • 若是查詢的時候使用範圍查詢,而且是最左前綴,那麼能夠用到索引,可是範圍後面的字段沒法用到索引。

這個原則能夠結合索引的原理來理解:Mysql索引是B+樹這種複合結構,當索引是聯合索引,好比【name,age,sex】時,B+樹是按照從左到右的順序創建索引樹的。當(張三,20,M)這樣的數據來檢索時,B+樹會優先根據name來肯定下一步的搜索方向,若是name相同再比較name和sex,最後獲得檢索的數據。但當(20,M)這樣的數據來的時候,mysql就不知道該查哪一個節點,由於創建索引的時候,name就是第一個比較因子,必須先根據name去肯定下一步去哪裏搜索。當(張三,M)這樣的數據來時,能夠根據name是「張三」,來肯定下一步的搜索,而後再去匹配性別是「M」的數據,所以只能用到聯合索引中name這個索引。

其餘原則

一、儘可能選擇區分度高的列做爲索引,區分度公式:count(distinct col)/count(*),表示字段不重複的比例,比例越大,咱們掃描的記錄數就越少,惟一性的列的區分度爲1。這就是爲何不建議在狀態,性別這樣區分度很小的列上創建索引的緣由。
二、索引列在sql語句中不能參與運算,不然會致使索引失效。例如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。應該改爲create_time = unix_timestamp(’2014-05-29’);
三、聯合索引比單個索引的性價比更高。例如,創建【A,B,C】這個聯合索引,至關於創建了【A】,【A,B】,【A,B,C】這三個索引。這就要求咱們儘可能的擴展索引而不是新建索引,具體狀況還需具體分析。
四、頻繁進行查詢的字段應該新建索引,與其餘表進行關聯的字段能夠考慮新建索引,查詢中排序的字段能夠考慮創建索引以提升排序的效率(這裏舉個例子,不少時候查詢記錄但願按照建立時間倒序返回,一般有人會這樣作order by create_time desc,可是若是create_time不是索引,而這個表有自增主鍵id,那麼order by id desc返回結果同樣,可是效率會提升)。

Mysql優化

致使sql執行慢的緣由

一、硬件問題:如網絡速度慢,內存不足,I/O吞吐量小,磁盤空間滿了等。
二、沒有使用索引或者索引失效。
三、數據過多(分庫分表)。
四、服務器或參數設置不當。

分析解決慢sql方法

一、先觀察,開啓慢查詢日誌,設置相應的閾值(好比超過3秒就是慢sql),再生產環境跑個一天,看看哪些sql比較慢。
二、explain和慢sql分析,好比sql語句寫的很差,沒有使用索引或者索引失效,或者sql語句太過複雜,關聯查詢和嵌套子查詢太多等等。
三、Show Profile是比explain更近一步的執行細節,能夠查詢到執行每個SQL都幹了什麼事,這些事分別花了多少秒。
四、找DBA或者運維對Mysql進行服務器的參數調優。

配置優化

基本配置

  • innodb_buffer_pool_size:這是安裝完InnoDB後第一個應該設置的選項。緩衝池是數據和索引緩存的地方:這個值越大越好,這能保證你在大多數的讀取操做時使用的是內存而不是硬盤。典型的值是5-6GB(8GB內存),20-25GB(32GB內存),100-120GB(128GB內存)。
  • innodb_log_file_size:這是redo日誌的大小。redo日誌被用於確保寫操做快速而可靠而且在崩潰時恢復。一直到MySQL 5.5,redo日誌的總尺寸被限定在4GB(默承認以有2個log文件)。這在MySQL5.6裏被提升了。若是你知道你的應用程序須要頻繁的寫入數據而且你使用的時MySQL5.6,一開始就能夠設置成4G。
  • max_connections:若是你常常看到'Too many connections'錯誤,是由於max_connections的值過低了由於應用程序沒有正確的關閉數據庫鏈接,你須要比默認的151鏈接數更大的值。max_connection值被設高了(例如1000或更高)以後一個主要缺陷是當服務器運行1000個或更高的活動事務時會變的沒有響應。

InnoDB配置

  • innodb_file_per_table:這項設置告知InnoDB是否須要將全部表的數據和索引存放在共享表空間裏(innodb_file_per_table = OFF) 或者爲每張表的數據單獨放在一個.ibd文件(innodb_file_per_table = ON)。每張表一個文件容許你在drop、truncate或者rebuild表時回收磁盤空間。這對於一些高級特性也是有必要的,好比數據壓縮。你不想讓每張表一個文件的主要場景是:有很是多的表(好比10k+)。
  • innodb_flush_log_at_trx_commit:默認值爲1,表示InnoDB徹底支持ACID特性。當你的主要關注點是數據安全的時候這個值是最合適的,好比在一個主節點上。可是對於磁盤(讀寫)速度較慢的系統,它會帶來很巨大的開銷,由於每次將改變flush到redo日誌都須要額外的fsyncs。將它的值設置爲2會致使不太可靠(reliable)由於提交的事務僅僅每秒才flush一次到redo日誌,但對於一些場景是能夠接受的,好比對於主節點的備份節點這個值是能夠接受。
  • innodb_flush_method:這項配置決定了數據和日誌寫入硬盤的方式。通常來講,若是你有硬件RAID控制器,而且其獨立緩存採用write-back機制,並有着電池斷電保護,那麼應該設置配置爲O_DIRECT;不然,大多數狀況下應將其設爲fdatasync(默認值)
  • innodb_log_buffer_size:這項配置決定了爲還沒有執行的事務分配的緩存。其默認值(1MB)通常來講已經夠用了,可是若是你的事務中包含有二進制大對象或者大文本字段的話,這點緩存很快就會被填滿並觸發額外的I/O操做。

執行計劃Explain

準備數據

CREATE TABLE `user_info` (

  `id`   BIGINT(20)  NOT NULL AUTO_INCREMENT,

  `name` VARCHAR(50) NOT NULL DEFAULT '',

  `age`  INT(11)              DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `name_index` (`name`)

)ENGINE = InnoDB DEFAULT CHARSET = utf8;

 

INSERT INTO user_info (name, age) VALUES ('xys', 20);

INSERT INTO user_info (name, age) VALUES ('a', 21);

INSERT INTO user_info (name, age) VALUES ('b', 23);

INSERT INTO user_info (name, age) VALUES ('c', 50);

INSERT INTO user_info (name, age) VALUES ('d', 15);

INSERT INTO user_info (name, age) VALUES ('e', 20);

INSERT INTO user_info (name, age) VALUES ('f', 21);

INSERT INTO user_info (name, age) VALUES ('g', 23);

INSERT INTO user_info (name, age) VALUES ('h', 50);

INSERT INTO user_info (name, age) VALUES ('i', 15);

 

CREATE TABLE `order_info` (

  `id`           BIGINT(20)  NOT NULL AUTO_INCREMENT,

  `user_id`      BIGINT(20)           DEFAULT NULL,

  `product_name` VARCHAR(50) NOT NULL DEFAULT '',

  `productor`    VARCHAR(30)          DEFAULT NULL,

  PRIMARY KEY (`id`),

  KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)

)ENGINE = InnoDB DEFAULT CHARSET = utf8;

 

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');

INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');

INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');

INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');

INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');

INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');
複製代碼

執行explain看看,索引使用狀況在possible_keys、key和key_len這三列。

分析explain

  • id
    id相同,執行順序由上而下

id不一樣,值越大越先執行

  • select_type

select_type總共有如下幾種類型:
一、SIMPLE:表示查詢不使用UNION或子查詢
二、PRIMARY:表示此查詢是最外層的查詢
三、SUBQUERY:表示此查詢是第一個查詢
四、UNION:表示此查詢是UNION第二或隨後的查詢 五、DEPENDENT UNION:UNION中的第二個或後面的查詢語句,取決於外面的查詢 六、UNION RESULT:UNION的結果 七、DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢,即子查詢依賴於外面查詢的結果 八、DERIVED:衍生,表示導出表的SELECT

  • table table表示查詢涉及的表或衍生的表

id=1的table derived2表示是由id=2的u和o衍生出來的

  • type
    type字段比較重要,它是判斷查詢是否高效的重要依據。
    一、system:表中只有一條數據,這種類型是特殊的const類型
    二、const:針對主鍵或惟一索引的等號條件進行掃描,最多隻返回一條數據,查詢速度極快,由於它僅僅讀取一次便可。
    三、eq_ref:此類型一般出如今多表join,表示對於前表的每個結果,都只能匹配到後表的一行結果,且查詢的比較操做一般是=,查詢效率較高。
    四、ref:此類型一般是多表join,針對非惟一索引,或者非主鍵索引,或者使用了最左前綴規則的索引。
    五、range:表示使用索引範圍查詢,經過索引字段範圍獲取表中部分數據記錄,這個類型一般出如今 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操做中。
    六、index:表示全索引掃描,和ALL類型相似,只不過All類型是全表掃描,index是掃描全部的索引,而不掃描數據。index 類型一般出如今:所要查詢的數據直接在索引樹中就能夠獲取到,而不須要掃描數據。當是這種狀況時,Extra 字段 會顯示 Using index。
    七、ALL:表示全表掃描,性能最差。當數據量大的時候,對數據庫會是巨大的災難。
  • possible_keys
    它表示mysql在查詢時,可能使用到的索引。注意:有些索引即便在possible_keys中出現,也不表示真正的會用到,mysql 在查詢時具體使用了哪些索引,由 key 字段決定。
  • key
    這是mysql在查詢時真正用到的索引
  • key_len
    表示mysql使用索引的字節數,這個字段能夠判斷組合索引是否徹底被使用
  • ref
    這個字段表示索引的哪一列被使用了,若是可能的話,會是一個常量。
  • rows
    這也是判斷sql性能好壞的一個重要字段。mysql 查詢優化器根據統計信息,估算 sql要查找到結果集須要掃描讀取的數據行數。原則上來講,rows越小查詢效率越高。
  • extra
    explain 中的不少額外的信息會在 extra 字段顯示,常見的有如下幾種:
    一、using filesort :表示 mysql 需額外的排序操做,不能經過索引順序達到排序效果。通常有 using filesort都建議優化去掉,由於這樣的查詢 cpu 資源消耗大。
    二、using index:覆蓋索引掃描,表示查詢在索引樹中就可查找所需數據,不用掃描表數據文件,每每說明性能不錯。
    三、using temporary:查詢有使用臨時表, 通常出現於排序, 分組和多表 join 的狀況, 查詢效率不高,建議優化。
    四、using where :代表使用了where過濾。

總結

常見的索引原則:

  • 選擇惟一性索引:惟一性索引的值是唯一的,能夠更快速的經過該索引肯定某條記錄。
  • 爲常常須要排序、分組和聯合操做的字段創建索引。
  • 爲常做爲查詢條件的字段創建索引。
  • 限制索引的數目:索引不是越多越好,太多的索引會使插入和更新變慢,由於須要維護索引樹。
  • 儘可能使用數據量少的索引,若是索引的值很長,那麼查詢的速度會受到影響。
  • 最左前綴匹配原則,是很是重要的原則。
  • 儘可能選擇區分度高的列做爲索引,區分度的公式是表示字段不重複的比例。
  • 索引列不能參與計算,保持列「乾淨」:帶函數的查詢不參與索引。
  • 刪除再也不使用或不多使用的索引。
  • 儘可能的擴展索引,而不是新建索引,能使用組合索引就不要新建索引。
相關文章
相關標籤/搜索