139 MySQL索引

1、初識索引

1.1 爲何要有索引

通常的應用系統,讀每每是比寫的速度要塊的,並且插入操做和通常的更新操做不多會出現性能問題,在生產環境中,咱們遇到的最多的,也就是最容易出現問題的,仍是一些比較複雜的查詢操做,所以對查詢語句的優化顯然是重中之重的,而優化加速查詢,就確定會提到數據表的索引問題mysql

1.2 什麼是索引?

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

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

索引至關於字典的音序表,若是想要查某一個字,直接去音序表中查這個子的讀音,第幾聲,查到第幾頁,這樣就縮小了查尋的範圍了,不然就須要從幾百頁中一個字一個字的去找,查詢的速度會大大的下降數據庫

若索引太多,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要數據結構

2、索引的原理

2.1 索引的原理

索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查字典,查火車車次,飛機航班等函數

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

數據庫也是同樣,但顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段查詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一會兒去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有算法基礎的同窗會想到搜索樹,其平均複雜度是logN,具備不錯的查詢性能。但這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮的。而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提升性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。mysql索引

2.2 磁盤IO與預讀

前面提到了訪問磁盤,那麼這裏先簡單介紹一下磁盤IO和預讀,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。下圖是計算機硬件延遲的對比圖,供你們參考:測試

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

3、索引的數據結構

4、MySQL索引管理

4.1 功能

  1. 對於MySQL來講,索引就是能加速查找數據的速度,優化數據的查詢速度
  2. mysql中的primary key,unique,聯合惟一也都是索引,這些索引除了加速查找之外,還有約束的功能

4.2 MySQL經常使用的索引

1.普通索引index:加速查找

2.惟一索引:(primary key / unique)

  • 主鍵索引 primary key :加速查找+約束(不爲空、不能重複)
  • 惟一索引 unique:加速查找+約束(不能重複)

3.聯合索引

  • primary key(id,name):聯合主鍵索引
  • unique(id,name):聯合惟一索引
  • index(id,name):聯合普通索引

4.3 各個索引的應用場景

舉個列子來講,好比是一個超市的會員卡系統

這個系統有一個會員表
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

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

4.4 索引的兩大類型hash與btree

咱們能夠在建立索引的時候,爲其指定索引類型,分兩類

1.hash類型的索引:查詢單條數據時塊,範圍查詢慢

2.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 等索引;

4.5 建立/刪除索引的語法

# 方法一:建立表時
    create table 表名(
           字段名1  數據類型 [完整性約束條件…],
           字段名2  數據類型 [完整性約束條件…],
           [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
           [索引名]  (字段名[(長度)]  [ASC |DESC]) 
           );
                
# 方法二:CREATE在已存在的表上建立索引
        CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                     ON 表名 (字段名[(長度)]  [ASC |DESC]) ;


# 方法三:ALTER TABLE在已存在的表上建立索引
        ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                             索引名 (字段名[(長度)]  [ASC |DESC]) ;
                             
# 刪除索引:DROP INDEX 索引名 ON 表名字;

4.6 示例

# 方式一
create table t1(
    id int,
    name char,
    age int,
    sex enum('male','female'),
    unique key uni_id(id),
    index ix_name(name) # index沒有key
);



# 方式二:CREATE在已存在的表上建立索引
create index ix_age on t1(age);


# 方式三:CREATE在已存在的表上建立索引
alter table t1 add index ix_sex(sex);
alter table t1 add index(sex);

# 查看t1表的索引
mysql> show create table t1;
| t1    | CREATE TABLE `t1` (
  `id` int(11) DEFAULT NULL,
  `name` char(1) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` enum('male','female') DEFAULT NULL,
  UNIQUE KEY `uni_id` (`id`),
  KEY `ix_name` (`name`),
  KEY `ix_age` (`age`),
  KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

5、測試索引

5.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<3000000)do
        insert into s1 values(i,'eva','female',concat('eva',i,'@user'));
        set i=i+1;
    end while;
END$$ # $$結束
delimiter ; # 從新聲明分號爲結束符號

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

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

1.在沒有索引的前提下測試如下查詢速度:

沒有索引的時候,mysql根本就不知道是否右2222222222這條記錄,因此mysql會把這張表從頭至尾的依次掃描一遍,而後再給結果,在這裏有多少個磁盤就會io對少次,因此查詢速度慢

mysql> select * from s1 where id=22222222222;
Empty set (0.13 sec)

2. 咱們在表建立時就沒有建立索引的狀況下給表中的字段新建一個索引:

咱們會發現,在表中已經存儲了大量的數據前提下,在去給表中的某一個字段創建索引的時候,建索引的速度會很慢

mysql>create index x on s1(id);
Query OK, 0 rows affected (1.49 sec)
Records: 0  Duplicates: 0  Warnings: 0

3. 在表索引創建完畢之後,去測試如下查詢的速度:

這個時候,咱們會發現創建完索引以後,查詢的速度明顯的提升了

mysql> select * from s1 where id=222222222211;
Empty set (0.00 sec)

注意:

  1. 咱們在給表創建索引以後,查詢數據時mysql先去索引表裏根據b+樹的索索原理快速搜索到id等於2222222222的記錄不存在,大大下降,於是速度明顯提高

  2. 咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了

  3. 須要注意,以下圖

    因爲表的email字段並無建立索引,因此經過email字段來查詢數據速度依舊很慢

    mysql> select * from s1 where email='aaaaa';
    Empty set (0.14 sec)

5.2 小結

  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個字節,類型爲長整型.

6、正確使用索引

6.1 索引未命中

並非說咱們建立了索引就必定會加快查詢速度,若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題:

  1. 範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、大於號、小於號

  1. ! =

  1. between and

  1. like

**儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄。**

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  | MUL | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> drop index x on s1;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index y on s1;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

分析緣由:

咱們編寫存儲過程爲表s1批量添加記錄,name字段的值均爲eva,也就是說name這個字段的區分度很低(gender字段也是同樣的,咱們稍後再搭理它)

回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,須要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...

而對於區分度低的字段,沒法找到大小關係,由於值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增長樹的高度,字段的區分度越低,則樹的高度越高。極端的狀況,索引字段的值都同樣,那麼b+樹幾乎成了一根棍。本例中就是這種極端的狀況,name字段全部的值均爲'nick'

如今咱們得出一個結論:爲區分度低的字段創建索引,索引樹的高度會很高,然而這具體會帶來什麼影響呢???

  1. 若是條件是name='xxxx',那麼確定是能夠第一時間判斷出'xxxx'是不在索引樹中的(由於樹中全部的值均爲'eva’),因此查詢速度很快
  2. 若是條件正好是name='eva',查詢時,咱們永遠沒法從樹的某個位置獲得一個明確的範圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數沒有多大區別,因此速度很慢

  3. 索引列不能在條件中參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)

  1. and/or
    1. and與or的邏輯
      • 條件1 and 條件2:全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立
      • 條件1 or 條件2:只要有一個條件成立則最終結果就成立
    2. and的工做原理
      • 條件:a = 10 and b = 'xxx' and c > 3 and d =4
      • 索引:製做聯合索引(d,a,b,c)
      • 工做原理:對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序
    3. or的工做原理
      • 條件:a = 10 or b = 'xxx' or c > 3 or d =4
      • 索引:製做聯合索引(d,a,b,c)
      • 工做原理:對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d

在左邊條件成立可是索引字段的區分度低的狀況下(name與gender均屬於這種狀況),會依次往右找到一個區分度高的索引字段,加速查詢。

相關文章
相關標籤/搜索