mysql性能優化系列7-索引與執行計劃

1. 索引

1.1 索引分類

(1)主鍵索引
根據主鍵建立索引,不允許重複,不允許空值
(2)唯一索引
索引的列值必須唯一,允許空值
(3)普通索引
普通列構建的索引,無限制
(4)全文索引
用大文本對象的列構建的索引
(5)組合索引
多個列組合構建索引,每個列不允許有空值。組合索引遵循最左前綴原則

1.2 實現原理

MySQL數據庫支持多種索引類型。
(1)哈希索引
只有memory存儲引擎支持哈希索引,用索引列的值計算hashCode,然後在hashCode對應位置存該值所在行數據的物理位置。因爲使用散列算法,速度快,但不支持範圍查找和排序。
(2)全文索引
FULLTEXT(全文)索引,只能用於MyISAM和InnoDB,數據量太大生成全文索引非常消耗時間和空間。對於文本大對象或者較大的CHAR類型數據,如果使用普通索引匹配文本,比如LIKE %word%,這樣響應時間會很長。這時候可以使用全文索引。
(3)BTree索引
對於BTree和B+Tree,這篇文章http://blog.jobbole.com/24006/不錯,下面內容借鑑這篇文章。
爲了描述B-Tree,首先定義一條數據記錄爲一個二元組[key, data],key爲記錄的鍵值,對於不同數據記錄,key是互不相同的;data爲數據記錄除key外的數據。那麼B-Tree是滿足下列條件的數據結構:

1. d爲大於1的一個正整數,稱爲B-Tree的度。

2. h爲一個正整數,稱爲B-Tree的高度。

3. 每個非葉子節點由n-1個key和n個指針組成,其中d<=n<=2d。

4. 每個葉子節點最少包含一個key和兩個指針,最多包含2d-1個key和2d個指針,葉節點的指針均爲null 。

5. 所有葉節點具有相同的深度,等於樹高h。

6. key和指針互相間隔,節點兩端是指針。

7. 一個節點中的key從左到右非遞減排列。

8. 所有節點組成樹結構。

9. 每個指針要麼爲null,要麼指向另外一個節點。

10. 如果某個指針在節點node最左邊且不爲null,則其指向節點的所有key小於v(key1),其中v(key1)爲node的第一個key的值。

11. 如果某個指針在節點node最右邊且不爲null,則其指向節點的所有key大於v(keym),其中v(keym)爲node的最後一個key的值。

12. 如果某個指針在節點node的左右相鄰key分別是keyi和keyi+1且不爲null,則其指向節點的所有key小於v(keyi+1)且大於v(keyi)。

看一個d=2的B-Tree示意圖。
在這裏插入圖片描述
由於B-Tree的特性,在B-Tree中按key檢索數據的算法非常直觀:首先從根節點進行二分查找,如果找到則返回對應節點的data,否則對相應區間的指針指向的節點遞歸進行查找,直到找到節點或找到null指針,前者查找成功,後者查找失敗。
(4)B+Tree
B-Tree有許多變種,其中最常見的是B+Tree,例如MySQL就普遍使用B+Tree實現其索引結構。與B-Tree相比,B+Tree有以下不同點:

1. 每個節點的指針上限爲2d而不是2d+1。

2. 內節點不存儲data,只存儲key;葉子節點不存儲指針。

在這裏插入圖片描述
帶有順序訪問指針的B+Tree
在這裏插入圖片描述
我們一般使用磁盤I/O次數評價索引結構的優劣。先從B-Tree分析,根據B-Tree的定義,可知檢索一次最多需要訪問h個節點。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。爲了達到這個目的,在實際實現B-Tree還需要使用如下技巧:
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O。B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存),漸進複雜度爲O(h)=O(logdN)。一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3)。綜上所述,用B-Tree作爲索引結構效率是非常高的。
MyISAM索引實現
MyISAM引擎使用B+Tree作爲索引結構,葉節點的data域存放的是數據記錄的地址。
在這裏插入圖片描述
在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。
MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然後以data域的值爲地址,讀取相應數據記錄。MyISAM的索引方式也叫做“非聚集”的,因爲索引文件和數據文件不在一個文件。
InnoDB索引實現
第一個重大區別是InnoDB的數據文件本身就是索引文件。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形。第二個與MyISAM索引的不同是InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作爲data域。
聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

1.3 語法

(1)查看索引

SHOW INDEX FROM table_name

(2)創建索引

CREATE  [UNIQUE ] INDEX indexName ON mytable(columnname(length));
ALTER TABLE 表名 ADD  [UNIQUE ]  INDEX [indexName] ON (columnname(length)) 

(3)刪除索引

DROP INDEX [indexName] ON mytable;

2. 執行計劃

SQL語句加上EXPLAIN關鍵字可以查詢執行計劃。執行計劃的結果如下:
在這裏插入圖片描述

2.1 ID列

ID列:描述select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序。

2.1.1 id相同

執行順序由上至下。
(1)執行語句

EXPLAIN select t2.* from t1,t2,t3  where t1.id = t2.id and t1.id = t3.id and t1.other_column = '';

(2)執行結果
在這裏插入圖片描述
ID列的值相同,先查詢t1,再t2,最後t3。

2.1.2 id完全不相同

如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行。
(1)執行語句

EXPLAIN select t2.* from  t2 where id = (select id from t1 where id =  (select t3.id from t3 where t3.other_column=''));

(2)執行結果
在這裏插入圖片描述

2.1.3 id不完全相同

id值越大越先執行,相同的由上向下執行。
(1)執行語句

EXPLAIN select t2.* from (select t3.id from t3 where t3.other_column = '') s1 ,t2 where s1.id = t2.id

(2)執行結果
在這裏插入圖片描述

2.2 select_type

Select_type表示查詢的類型。類型如下:

  • simple:簡單的查詢,不包括子查詢和union查詢
  • primary:查詢包含複雜的子查詢,最外層查詢爲primary
  • union:若第二個SELECT出現在UNION之後,則被標記爲UNION;若UNION包含在 FROM子句的子查詢中,外層SELECT將被標記爲:DERIVED
  • subquery:在SELECT或WHERE列表中包含了子查詢
  • derived:在FROM列表中包含的子查詢的標記
  • UNION RESULT:從UNION表獲取結果的SELECT

2.2.1 SIMPLE

(1)執行語句

EXPLAIN select * from t1

(2)執行結果
在這裏插入圖片描述
查詢中不包含子查詢或者UNION

2.2.2 PRIMARY與SUBQUERY

PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記爲
SUBQUERY:在SELECT或WHERE列表中包含了子查詢
(1)執行語句

EXPLAIN select t1.*,(select t2.id from t2 where t2.id = 1 ) from t1 

(2)執行結果
在這裏插入圖片描述

2.2.3 DERIVED

在FROM列表中包含的子查詢被標記爲DERIVED(衍生),MySQL會遞歸執行這些子查詢, 把結果放在臨時表裏。

(1)執行語句

EXPLAIN select t1.* from t1 ,(select t2.* from t2 where t2.id = 1 ) s2  where t1.id = s2.id

(2)執行結果
在這裏插入圖片描述

2.2.4 UNION RESULT 與UNION

UNION:若第二個SELECT出現在UNION之後,則被標記爲UNION;
UNION RESULT:從UNION表獲取結果的SELECT
(1)執行語句

EXPLAIN select * from t1 UNION  select * from t2

(2)執行結果
在這裏插入圖片描述

2.3 table

顯示這一行的數據是關於哪張表的

2.4 Type

type顯示的是訪問類型,是較爲重要的一個指標,結果值從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。
常見的是system>const>eq_ref>ref>range>index>ALL,一般來說,得保證查詢至少達到range級別,最好能達到ref。

2.4.1 System與const

System:表只有一行記錄(等於系統表),這是const類型的特列。
Const:表示通過索引一次就找到了
(1)執行語句

EXPLAIN SELECT * from (select * from t2 where id = 1) d1;

(2)執行結果
在這裏插入圖片描述

2.4.2 eq_ref

唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描

(1)執行語句

EXPLAIN SELECT * from t1,t2 where t1.id = t2.id;

(2)執行結果

在這裏插入圖片描述
注意表的執行順序。

2.4.3 Ref

非唯一性索引掃描,返回匹配某個單獨值的所有行。
(1)執行語句

EXPLAIN select count(DISTINCT col1) from ta where col1 = 'ac'

(2)執行結果
在這裏插入圖片描述

2.4.4 Range

只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。一般就是在你的where語句中出現了between、<、>、in等的查詢
這種範圍掃描索引掃描比全表掃描要好,因爲它只需要開始於索引的某一點,而結束語另一點,不用掃描全部索引。
(1)執行語句

EXPLAIN select * from t1 where id BETWEEN 30 and 60

(2)執行結果
在這裏插入圖片描述

2.4.5 Index

當查詢的結果全爲索引列的時候,雖然也是全部掃描,但是隻查詢的索引庫,而沒有去查詢其他數據。

(1)執行語句

EXPLAIN select col1,col2 from ta

(2)執行結果
在這裏插入圖片描述

2.4.6 All

遍歷全表以找到匹配的行
(1)執行語句

EXPLAIN select * from t1

(2)執行結果
在這裏插入圖片描述

2.5 possible_keys與Key

possible_keys是可能使用的key,Key爲實際使用的索引。如果爲NULL,則沒有使用索引。如果查詢中使用了覆蓋索引,則該索引和查詢的select字段重疊。
(1)執行語句

EXPLAIN select col1,col2 from ta

(2)執行結果
在這裏插入圖片描述

2.6 key_len

key_len表示索引使用的字節數,根據這個值,就可以判斷索引使用情況,特別是在組合索引的時候,判斷所有的索引字段是否都被查詢用到。char和varchar跟字符編碼也有密切的聯繫,latin1佔用1個字節,gbk佔用2個字節,utf8佔用3個字節。
(1)索引字段爲char類型並且字段定義不爲Null

explain select * from s1 where name='enjoy';

在這裏插入圖片描述
name這一列爲char(10),字符集爲utf-8佔用3個字節,Keylen=10*3。
(2)索引字段爲char類型並且字段定義可以爲Null

explain select * from s2 where name='enjoyedu';

在這裏插入圖片描述
name這一列爲char(10),字符集爲utf-8佔用3個字節,外加需要存入一個null值,Keylen=10*3+1(null) 結果爲31。
(3)索引字段爲varchar類型並且字段定義不爲Null

explain select * from s3 where name='enjoyeud';

在這裏插入圖片描述
Keylen=varchar(n)變長字段+不允許Null=n*(utf8=3,gbk=2,latin1=1)+2。varchar變長字段需要額外的2個字節。
(4)索引字段爲varchar類型並且字段定義可以爲Null

explain select * from s4 where name='enjoyeud';

在這裏插入圖片描述
Keylen=varchar(n)變長字段+允許Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2。

總結:
變長字段需要額外的2個字節(VARCHAR值保存時只保存需要的字符數,另加一個字節來記錄長度(如果列聲明的長度超過255,則使用兩個字節),所以VARCAHR索引長度計算時候要加2),固定長度字段不需要額外的字節。而NULL都需要1個字節的額外空間,所以索引字段最好不要爲NULL,因爲NULL讓統計更加複雜並且需要額外的存儲空間。複合索引有最左前綴的特性,如果複合索引能全部使用上,則是複合索引字段的索引長度之和,這也可以用來判定複合索引是否部分使用,還是全部使用。
(5)整數/浮點數/時間類型
同樣,NOT NULL=字段本身的字段長度。NULL=字段本身的字段長度+1(因爲需要有是否爲空的標記,這個標記需要佔用1個字節)。datetime類型在5.6中字段長度是5個字節,datetime類型在5.5中字段長度是8個字節。
在這裏插入圖片描述
在這裏插入圖片描述

2.7 Ref

顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值。
(1)執行語句

EXPLAIN select * from s1 ,s2 where s1.id = s2.id and s1.name = 'enjoy'

(2)執行結果
在這裏插入圖片描述
第一條表明使用了s1表name屬性的常量。第二條表明使用了test庫s1表的id列。

2.8 Rows

根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數。

2.9 Extra

其他信息,包含不適合在其他列中顯示但十分重要的信息。

  • distinct:在select部分使用了distinc關鍵字
  • using filesort:mysql對數據使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql無法利用索引完成的排序操作成爲“文件排序” 。常見於order by和group by語句中。
  • using index:查詢時不需要回表查詢,直接通過索引就可以獲取查詢的數據。也就是覆蓋索引
  • using join buffer:使用了連接緩存
  • using intersect:表示使用and的各個索引的條件時,該信息表示是從處理結果獲取交集
  • using union:表示使用or連接各個使用索引的條件時,該信息表示從處理結果獲取並集
  • using temporary:表示使用了臨時表存儲中間結果。臨時表可以是內存臨時表和磁盤臨時表,執行計劃中看不出來。mysql在對查詢結果排序時候使用臨時表
  • using where:表示存儲引擎返回的記錄並不是所有的都滿足查詢條件,需要根據where過濾
  • impossible where:where子句總是false

這裏挑幾個重要的說明下。

2.9.1 Using filesort

mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中無法利用索引完成的排序操作稱爲“文件排序”。有Using filesort時代表這裏可以優化。
這裏對列col1,col2,col3建立了聯合索引。
(1)執行語句

EXPLAIN select col1 from ta where col1='ac' order by col3

EXPLAIN select col1 from ta where col1='ac' order by col2,col3

(2)執行結果

第一個語句結果:
在這裏插入圖片描述
第二個語句結果:
在這裏插入圖片描述

2.9.2 Using temporary

使了用臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by。
(1)執行語句

EXPLAIN select col1 from ta where col1 in('ac','ab','aa') GROUP BY col2

EXPLAIN select col1 from ta where col1 in('ac','ab','aa') GROUP BY col1,col2

(2)執行結果

第一個語句結果:
在這裏插入圖片描述
第二個語句結果:
在這裏插入圖片描述

2.9.3 Using index

表示相應的select操作中使用了覆蓋索引(Covering Index),避免訪問了表的數據行,效率不錯。如果同時出現using where,表明索引被用來執行索引鍵值的查找;。如果沒有同時出現using where,表明索引用來讀取數據而非執行查找動作。
注意:
如果要使用覆蓋索引,一定要注意select列表中只取出需要的列,不可select *,因爲如果將所有字段一起做索引會導致索引文件過大,查詢性能下降,而且會嚴重影響修改維護的性能。

(1)執行語句

EXPLAIN select col2 from ta 

EXPLAIN select col2 from ta where col1 = 'ab'

(2)執行結果

第一個語句結果:
在這裏插入圖片描述
第二個語句結果:
在這裏插入圖片描述

2.9.4 Using where 與 using join buffer

Using where表明使用了where過濾。using join buffer使用了連接緩存。
(1)執行語句

EXPLAIN select * from t1  JOIN t2  on t1.other_column = t2.other_column

(2)執行結果
在這裏插入圖片描述

3. SQL優化

(1)儘量全值匹配
查詢數據儘量使用條件精確匹配
(2)最佳左前綴法則
如果索引了多列,要遵守最左前綴法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
(3)不在索引列上做任何操作
不在索引列上做任何操作(計算、函數、(自動or手動)類型轉換),會導致索引失效而轉向全表掃描
(4)範圍條件放最後
存儲引擎不能使用索引中範圍條件右邊的列
(5)儘量使用覆蓋索引
(6)不等於要甚用
mysql 在使用不等於(!= 或者<>)的時候無法使用索引會導致全表掃描
(7)Like查詢要當心
like以通配符開頭(’%abc…’)mysql索引失效會變成全表掃描的操作
(8)字符類型加引號
字符串不加單引號索引失效
(9)OR改UNION效率高
(10)批量插入
提交前關閉自動提交,儘量使用批量insert語句,可以使用MyISAM存儲引擎。如果需要大批量導入數據可以使用LOAD DATA INFLIE,比一般的insert語句快20倍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章