MySQL 之 索引

一、爲何要有索引

​ 對查詢語句的優化,加速查詢mysql

二、什麼是索引

​ 索引在MySQL中也叫是一種‘鍵’,是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要。sql

​ 索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。數據庫

​ 索引至關於字典的音序表,若是要查某個字,若是不使用音序表,則須要從幾百頁中逐頁去查。性能優化

三、索引的原理

(1)、 索引原理

​ 本質都是:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,就能夠老是用同一種查找方式來鎖定數據。數據結構

(2)、 磁盤IO與預讀

​ 磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2= 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17= 9ms左右。但一臺500 -MIPS的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。函數

​ 考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,還把相鄰的數據也都讀取到內存緩衝區內,由於由局部預讀性原理可知,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據稱之爲一頁(page)。具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫助。性能

四、索引的數據結構

(1)、樹

​ 樹狀圖是一種數據結構,它是由n(n>=1)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。mysql索引

​ 它具備如下的特色:每一個節點有零個或多個子節點;沒有父節點的節點稱爲根節點;每個非根節點有且只有一個父節點;除了根節點外,每一個子節點能夠分爲多個不相交的子樹。測試

(2)、B樹

​ 平衡樹 balance tree - B樹大數據

(3)、B+樹

​ B+樹是經過二叉查找樹,再由平衡二叉樹,B樹演化而來, 是爲了更好的處理範圍問題在b樹的基礎上有所優化。mysql 中innodb存儲引擎的全部的索引樹都是b+樹

五、彙集索引與輔助索引

​ 在數據庫中,B+樹的高度通常都在2~4層,這也就是說查找某一個鍵值的行記錄時最多隻須要2到4次IO,當前通常的機械硬盤每秒至少能夠作100次IO,2~4次的IO意味着查詢時間只須要0.02~0.04秒。

​ 數據庫中的B+樹索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index)

(1)、彙集索引與輔助索引的相同點:

​ 彙集索引與輔助索引相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着全部的數據。

(2)、彙集索引與輔助索引的不相同點:

​ 彙集索引與輔助索引不一樣的是:葉子結點存放的是不是一整行的信息。

<1>、 彙集索引/聚簇索引:葉子節點會存儲整行數據 ----- innodb 的主鍵
# InnoDB存儲引擎表是索引組織表,即表中數據按照主鍵順序存放。
而彙集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即爲整張表的行記錄數據,也將彙集索引的葉子結點稱爲數據頁。
彙集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構同樣,每一個數據頁都經過一個雙向鏈表來進行連接。
    
# 若是未定義主鍵,MySQL取第一個惟一索引(unique)並且只含非空列(NOT NULL)做爲主鍵,InnoDB使用它做爲聚簇索引。   
# 若是沒有這樣的列,InnoDB就本身產生一個這樣的ID值,它有六個字節,並且是隱藏的,使其做爲聚簇索引。

# 因爲實際的數據頁只能按照一棵B+樹進行排序,所以每張表只能擁有一個彙集索引。
在多數狀況下,查詢優化器傾向於採用彙集索引。由於彙集索引可以在B+樹索引的葉子節點上直接找到數據。
此外因爲定義了數據的邏輯順序,彙集索引可以特別快地訪問針對範圍值得查詢。
<2>、 輔助索引/非彙集索引:除了主鍵以外的普通索引都是輔助索引,一個索引沒辦法查到整行數據,須要回彙集索引再查一次(回表)
表中除了彙集索引外其餘索引都是輔助索引(Secondary Index,也稱爲非彙集索引),與彙集索引的區別是:輔助索引的葉子節點不包含行記錄的所有數據。
葉子節點除了包含鍵值之外,每一個葉子節點中的索引行中還包含一個書籤(bookmark)。該書籤用來告訴InnoDB存儲引擎去哪裏能夠找到與索引相對應的行數據。
因爲InnoDB存儲引擎是索引組織表,所以InnoDB存儲引擎的輔助索引的書籤就是相應行數據的彙集索引鍵。
輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引,但只能有一個彙集索引。當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉子級別的指針得到只想主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄。
<3>、聚焦索引和非聚焦索引的區別
# 彙集索引
1.紀錄的索引順序與物理順序相同
   所以更適合between and和order by操做
2.葉子結點直接對應數據
 從中間級的索引頁的索引行直接對應數據頁
3.每張表只能建立一個彙集索引

# 非彙集索引
1.索引順序和物理順序無關
2.葉子結點不直接指向數據頁
3.每張表能夠有多個非彙集索引,須要更多磁盤和內容
   多個索引會影響insert和update的速度

六、MySQL索引管理

(1)、索引功能

1. 索引的功能就是加速查找
2. mysql中的primary key,unique,聯合惟一也都是索引,這些索引除了加速查找之外,還有約束的功能

(2)、MySQL經常使用的索引

普通索引 INDEX:加速查找

惟一索引:
    -主鍵索引 PRIMARY KEY:加速查找+約束(不爲空、不能重複)
    -惟一索引 UNIQUE:加速查找+約束(不能重複)

聯合索引:
    -PRIMARY KEY(id,name):聯合主鍵索引
    -UNIQUE(id,name):聯合惟一索引
    -INDEX(id,name):聯合普通索引

(3)、各個索引的應用場景

# 舉個例子來講,好比你在爲某商場作一個會員卡的系統。這個系統有一個會員表,有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(11)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麼這個 會員編號,做爲主鍵,使用 PRIMARY
會員姓名 若是要建索引的話,那麼就是普通的 INDEX
會員身份證號碼 若是要建索引的話,那麼能夠選擇 UNIQUE (惟一的,不容許重複)

# 除此以外還有全文索引,即 FULLTEXT
會員備註信息 , 若是須要建索引的話,能夠選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,若是就一兩行字的,普通的 INDEX 也能夠。
但其實對於全文搜索,咱們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來作全文搜索。

# 其餘的如空間索引SPATIAL,瞭解便可,幾乎不用

(4)、索引的兩大類型 hash 與 btree

# 咱們能夠在建立上述索引的時候,爲其指定索引類型,分兩類
hash 類型的索引:查詢單條快,範圍查詢慢
btree 類型的索引:b+樹,層數越多,數據量指數級增加(咱們就用它,由於innodb默認支持它)

#不 同的存儲引擎支持的索引類型也不同
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;

(5)、操做索引: 建立和刪除

<1> 建立:create index 索引名 on 表名(字段名);
create index  id on s1(id);
alter table s1 add index ix_sex(sex);

<2> 刪除: drop index 索引名 on 表名;

drop index  id on 表名;

七、測試索引

(1)、準備數據

# 1. 準備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

# 2. 建立存儲過程,實現批量插入記錄
delimiter $$ #聲明存儲過程的結束符號爲$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<30000000)do
        insert into s1 values(i,'cai','male',concat('cai',i,'@yong'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #從新聲明分號爲結束符號

# 3. 查看存儲過程
show create procedure auto_insert1\G 

# 4. 調用存儲過程
call auto_insert1();

(2)、 在沒有索引的前提下測試查詢速度

# 無索引:mysql根本就不知道究竟是否存在id等於333333333的記錄,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)

(3)、 在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢

img

(4)、 在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯

img

注意:

  • mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高
  • 能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多大
  • 須要注意,以下圖

img

(5)、總結

# 1.必定是爲搜索條件的字段建立索引,好比select * from s1 where id = 333;就須要爲id加上索引
# 2.在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快
好比create index idx on s1(id);會掃描表中全部的數據,而後以id爲數據項,建立索引結構,存放於硬盤的表中。
建完之後,再查詢就會很快了。
#3. 須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI
MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件自己就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以innodb表數據文件自己就是主索引。
由於inndob的數據文件要按照主鍵彙集,因此innodb要求表必需要有主鍵(Myisam能夠沒有),若是沒有顯式定義,則mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則mysql會自動爲innodb表生成一個隱含字段做爲主鍵,這字段的長度爲6個字節,類型爲長整型.

八、正確使用索引

(1).只有對建立了索引的列進行條件篩選的時候效率纔會高

(2).索引對應的列作條件不能參與運算、不能使用函數

(3).當某一列的區分度很是小(重複率高),不適合建立索引

(4).當範圍做爲條件的時候,查詢結果的範圍越大越慢,越小越快

(5).like關鍵字:若是使用%/ 開頭都沒法命中索引

(6).多個條件:若是隻有一部分建立了索引,條件用and相連,那麼能夠提升查詢效率。(若是用or相連,不能提升查詢效率)

and
   select count(*) from s1 where id=1000000  and email = 'eva1000000@oldboy';   # 查詢速度加快
or
   select count(*) from s1 where id=1000000  or email = 'eva1000000@oldboy';

(7).聯合索引:聯合索引是指對錶上的多個列合起來作一個索引。聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處僅在於有多個索引列。

creat index ind_mix on s1(id,name,email);
select count(*) from s1 where id=1000000  and email = 'eva1000000@oldboy';  # 快
select count(*) from s1 where id=1000000  or email = 'eva1000000@oldboy';   # 慢   條件不能用or
select count(*) from s1 where id=1000000;                                   # 快
select count(*) from s1 where email = 'eva1000000@oldboy';                  # 慢   要服從最左前綴原則
select count(*) from s1 where id>1000000  and email = 'eva1000000@oldboy';  # 慢   從使用了範圍的條件開始以後的索引都失效
相關文章
相關標籤/搜索