搞懂Mysql索引原理及應用,這一篇就夠了!


什么是索引?

索引是對數據庫表中一列或多列的值進行排序的一種數據結構,使用索引可以快速訪問數據庫表中的特定信息。

我們創建索引的時候是這樣的:

create index index_name on table_name(column)

可以這樣想:索引是取出了一列或者幾個特殊的列,然后把他們重命名為index_name,然后存起來,存的方式是用特殊的數據結構,比如二叉樹。

索引中包含了一個表中列的值和它的地址的值,並且這些值存儲在一個數據結構中。索引的組織形式有二叉樹、哈希、B-樹、B+樹。如果索引存的是一列就叫做單索引,多列就是復合索引。

索引一般以文件形式存在磁盤中(也可以存於內存),存儲索引的原理大致概括為以空間換時間。

索引能干什么?

執行這樣一條語句:

select name from student where stuno = "003"

在一張學生表里面找學號是003的學生,假如一共到100號學生。那么DBMS會生成一個類似指針的東西,從stuno ="001"一直到最后,進行全表掃描。那么為什么到stuno="003"不停止呢?因為數據庫在未添加索引的時候默認執行的是全量搜索,select執行就就是全表掃描,即使掃描主鍵也一樣。

如果我們在stuno字段上創建了一個索引,假如索引的底層存儲結構是二叉樹,那么只需要log2(100)次就能查到所需要的數據。因此,建立索引的目的就是加快對表中記錄的查找或排序。

當然索引不是萬能的,它的缺點有:創建和維護索引需要時間和空間成本,每次增刪改索引都需要進行動態維護。

索引的分類

索引類型有:主鍵索引,普通索引,復合索引,全文索引,唯一索引

  1. 主鍵索引

    --創建1
    crete table student(id int primary key auto_increment, name varchar(20) not null default)
    
    --創建2:先創建表,后創建index
    alter table student add primary key(id)
    
    --在創建主鍵的時候,就會為我們創建好主鍵索引。
    
    
  2. 唯一索引

    唯一索引和普通索引類似,主要區別在於,唯一索引限制列的值必須唯一,但允許存在空值(只能有一個)。主鍵索引不允許有空值。

  3. 全文索引

    在執行模糊查詢的時候,如like "value%",這種情況下,需要考慮使用全文搜索的方式進行優化。全文搜索在MySQL中是一個FULLTEXT類型索引。全文索引主要用來查找文本中的關鍵字,而不是直接與索引中的值進行比較,它更像是一個搜索引擎,而不是簡單的where語句的參數匹配。目前只有char/vachar/text列上可以創建全文索引,默認Mysql不支持中文全文搜索。Mysql全文搜索只是一個臨時方案,對於全文搜索場景,更專業的做法是使用全文搜索引擎,如ElasticSearch。

索引底層原理

哈希表

select * from user where id=7;

哈希算法首先計算出id=7的無力地址addr=hash(7)=4213,而4213映射的物理地址是0x77,然后就可以找到我們想要的數據了。

從時間復雜度來看,哈希算法時間復雜度為o(1),速度非常快,但是Mysql並沒有采取哈希作為其底層算法。因為考慮到數據檢索有一個常用的手段就是范圍查找,使用哈希就沒有辦法做到高效的范圍查找。比如:

select * from user where id>3

BST樹

二叉查找樹的時間復雜度是O(logn),從檢索效率上看是能做到高效檢索,而二叉樹所有左子樹比根節點小,右子樹比根節點大的特性使得它也支持范圍查找。

但是二叉樹有個致命缺點:極端情況下會退化為線性表。在數據庫中,數據的自增是一個很常見的形式,比如主鍵id一般默認都是自增的,如果采取二叉樹作為索引的底層數據結構,那上面介紹的不平衡狀態導致的線形查找問題必然出現!!!因此,簡單的二叉樹不能直接用於實現Mysql底層索引的。

AVL樹和紅黑樹

學過數據結構的都知道,解決二叉樹不平衡導致的性能問題,就是二叉平衡樹。通過調整樹的結構,來達到高度的平衡狀態,但是頻繁的調整樹的結構也是一種開銷,而紅黑樹就是舍去一部分的平衡性來獲得低的調整開銷。但是紅黑樹也存在“右傾現象”,查找性能又會降低。因此在查找性能和調整樹的開銷之間很難找到一種平衡,也不適合作為Mysql底層索引結構。

img

有人會問:數據庫查詢數據的瓶頸在於磁盤IO,調整樹的開銷存在於增刪,而查找不需要調整,為什么AVL樹不適合呢?

答:AVL不是不適合,而是有更好的數據結構。我們知道磁盤IO有一個特點,就是從磁盤讀取1B的數據和1KB的數據所消耗的時間基本是一樣的,那我們的優化思路就可以改為:盡可能在一次磁盤 IO 中多讀一點數據到內存。這個直接反映到樹的結構就是,每個節點能存儲的 key 可以適當增加,這就是B樹、B+樹的設計原理了。

img

B樹和B+樹

B樹和B+樹又被稱為多路查找,一個節點存儲了多個key來減少磁盤IO,從而提高檢索速度。

img

B樹和B+樹有什么區別呢?

  • B樹一個節點里面存的是(key&value),而B+樹存儲的是(key),所以B樹里一個節點存不了很多(key&value),但是B+樹一個節點能存儲很多(key),B+樹葉子節點存所有的數據。

    img

  • B+樹的葉子節點是(key&value,並用一個鏈表串聯起來。

    img

通過 B 樹和 B+樹的對比我們看出,B+樹節點存儲的是索引,在單個節點存儲容量有限的情況下,單節點也能存儲大量索引,使得整個 B+樹高度降低,減少了磁盤 IO。其次,B+樹的葉子節點是真正數據存儲的地方,葉子節點用了鏈表連接起來,這個鏈表本身就是有序的,在數據范圍查找時,更具備效率。因此 Mysql 的索引用的就是 B+樹,B+樹在查找效率、范圍查找中都有着非常不錯的性能。

Mysql存儲引擎實現

Mysql底層數據引擎以插件形式設計,最常見的就是Innodb引擎和Myisam引擎,用戶可以根據個人需求選擇不同的引擎作為Mysql數據表的底層引擎。我們剛分析了,B+樹作為 Mysql 的索引的數據結構非常合適,但是數據和索引到底怎么組織起來也是需要一番設計,設計理念的不同也導致了 Innodb 和 Myisam 的出現,各自呈現獨特的性能。

MyISAM 雖然數據查找性能極佳,但是不支持事務處理。Innodb 最大的特色就是支持了 ACID 兼容的事務功能,而且他支持行級鎖。Mysql 建立表的時候就可以指定引擎,比如下面的例子,就是分別指定了 Myisam 和 Innodb 作為 user 表和 user2 表的數據引擎。

create table 'user' (
'id' int not null default '0'
) engine=myisam default charset=utf8

Innodb創建表后生成的文件有:

  • frm:創建表的語句
  • idb:表里面的數據+索引文件

Myisam創建表后生成的文件有:

  • frm:創建表的語句
  • MYD:表里面的數據文件
  • MYI:表里面的索引文件

從生成的文件看來,這兩個引擎底層數據和索引的組織方式並不一樣,MyISAM 引擎把數據和索引分開了,一人一個文件,這叫做非聚集索引方式;Innodb 引擎把數據和索引放在同一個文件里了,這叫做聚集索引方式。下面將從底層實現角度分析這兩個引擎是怎么依靠 B+樹這個數據結構來組織引擎實現的。

MyISAM引擎的底層實現(非聚集索引方式)

MyISAM 用的是非聚集索引方式,即數據和索引落在不同的兩個文件上。MyISAM 在建表時以主鍵作為 KEY 來建立主索引 B+樹,樹的葉子節點存的是對應數據的物理地址。我們拿到這個物理地址后,就可以到 MyISAM 數據文件中直接定位到具體的數據記錄了。

img

當我們為某個字段添加索引時,我們同樣會生成對應字段的索引樹,該字段的索引樹的葉子節點同樣是記錄了對應數據的物理地址,然后也是拿着這個物理地址去數據文件里定位到具體的數據記錄。

Innodb引擎的底層實現(聚集索引方式)

InnoDB 是聚集索引方式,因此數據和索引都存儲在同一個文件里。首先 InnoDB 會根據主鍵 ID 作為 KEY 建立索引 B+樹,如左下圖所示,而 B+樹的葉子節點存儲的是主鍵 ID 對應的數據,比如在執行 select * from user_info where id=15 這個語句時,InnoDB 就會查詢這顆主鍵 ID 索引 B+樹,找到對應的 user_name='Bob'。

img

這是建表的時候 InnoDB 就會自動建立好主鍵 ID 索引樹,這也是為什么 Mysql 在建表時要求必須指定主鍵的原因。當我們為表里某個字段加索引時 InnoDB 會怎么建立索引樹呢?比如我們要給 user_name 這個字段加索引,那么 InnoDB 就會建立 user_name 索引 B+樹,節點里存的是 user_name 這個 KEY,葉子節點存儲的數據的是主鍵 KEY。注意,葉子存儲的是主鍵 KEY!拿到主鍵 KEY 后,InnoDB 才會去主鍵索引樹里根據剛在 user_name 索引樹找到的主鍵 KEY 查找到對應的數據。

問題來了,為什么 InnoDB 只在主鍵索引樹的葉子節點存儲了具體數據,但是其他索引樹卻不存具體數據呢,而要多此一舉先找到主鍵,再在主鍵索引樹找到對應的數據呢?

其實很簡單,因為 InnoDB 需要節省存儲空間。一個表里可能有很多個索引,InnoDB 都會給每個加了索引的字段生成索引樹,如果每個字段的索引樹都存儲了具體數據,那么這個表的索引數據文件就變得非常巨大(數據極度冗余了)。從節約磁盤空間的角度來說,真的沒有必要每個字段索引樹都存具體數據,通過這種看似“多此一舉”的步驟,在犧牲較少查詢的性能下節省了巨大的磁盤空間,這是非常有值得的。

兩種存儲引擎對比

在進行 InnoDB 和 MyISAM 特點對比時談到,MyISAM 查詢性能更好,從上面索引文件數據文件的設計來看也可以看出原因:MyISAM 直接找到物理地址后就可以直接定位到數據記錄,但是 InnoDB 查詢到葉子節點后,還需要再查詢一次主鍵索引樹,才可以定位到具體數據。等於 MyISAM 一步就查到了數據,但是 InnoDB 要兩步,那當然 MyISAM 查詢性能更高。

使用索引的注意事項!!!!

Mysql索引使用策略

  1. 最好全值匹配--索引怎么建我怎么用。
  2. 最佳左前綴法則--如果是多列復合索引,要遵守最左前綴法則。指的是查詢要從索引的最左前列開始並且不跳過索引中的列。
  3. 不在索引列上做任何操作(計算,函數,(自動或者手動)類型裝換),會導致索引失效而導致全表掃描。
  4. 存儲引擎不能使用索引中范圍條件右邊的列。--范圍之后索引失效(< ,>,between and)
  5. 盡量使用覆蓋索引--索引和查詢列一致,減少select *。--按需取數據用多少取多少
  6. 在MYSQL使用不等於(<,>,!=)的時候無法使用索引,會導致索引失效
  7. is null或者is not null 也會導致無法使用索引
  8. like以通配符開頭('%abc...')MYSQL索引失效會變成全表掃描的操作。--覆蓋索引
  9. 隱式轉換索引失效:字符串不加單引號
  10. where條件少用or,用它來連接時索引會失效

Mysql索引使用原則

  1. 復合索引:選擇索引列的順序

    1)盡量把字段長度小的列放在聯合索引的最左側(因為字段長度越小,一頁能存儲的數據量越大,IO性能也就越好)
    
    2)區分度最高的放在聯合索引的最左側(區分度=列中不同值的數量/列的總行數)
    
    3)使用最頻繁的列放到聯合索引的左側(這樣可以比較少的建立一些索引)
    
  2. 表關聯查詢

    1)類型和大小要相同,可以使用索引。
    
    VARCHAR(10)和 CHAR(10)大小相同,但 VARCHAR(10)與 CHAR(15)不相同。
    
    2)字符串列之間比較,兩列應使用相同的字符集。例如,將utf8列與 latin1列進行比較會不使用索引。
    
    3)將字符串列與時間或數字列進行比較時,在沒有轉換情況下,不使用索引。
    

什么字段適合創建索引?

  1. 較為頻繁的作為查詢條件的字段應該創建索引
  2. 唯一性太差的字段不適合單獨創建索引,即使該字段頻繁作為查詢條件
  3. 更新非常頻繁的字段不適合創建索引


免責聲明!

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



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