MySQL索引做爲數據庫優化的經常使用手段之一在項目優化中常常會被用到, 可是如何創建高效索引,有效的使用索引以及索引優化的背後究竟是什麼原理?此次咱們深刻數據庫索引,從索引的數據結構開始提及.node
索引爲何能提升查詢效率?當咱們有一個索引index(a)
以後,寫一個查詢語句where a = 4
.索引是怎麼工做的.在學數據結構的時候學過紅黑樹,這是現現在使用的最普遍了的數據結構之一了,緣由就在於它查詢高效.mysql
上圖就是一顆紅黑樹.它有一個基本特性 :sql
某個節點的左子樹的值必然都小於當前節點的值數據庫
某個節點的右子樹的值必然都大於當前節點的值數據結構
因此當咱們想要找值爲6的節點的時候,即相似:where node=6
.它從節點13開始,性能
只要查詢的值小於當前節點就順着左子數查找優化
要查詢的節點大於當前節點就順着當前節點的右子樹查找.spa
因此只要通過4次的查找(13,8,1,6)就能夠找到6.因爲紅黑樹的神奇特性,因此在查找的時候能夠在O(log n)的時間內作查找操做.如今咱們考慮一下,若是咱們索引使用了紅黑樹以後,它怎麼幫助咱們提升查詢效率.指針
首先,每一個節點必然是一個結構體,包含以下屬性:code
node { node *left, //指向其左子樹的指針 node *right, //指向其右子樹的指針 Point *p //指向表中某一行的指針. int data // 當前節點的值 }
當咱們對某個創建一個索引的時候,MySQL就會把這個字段的全部值創建起一個紅黑樹,紅黑樹中的每一個節點會有一個指針,這個指針指向當前數據的地址,好比A21是13所在行的指針,那麼紅黑樹中值爲13的節點就有一個指針指向這一行.
如今咱們來模擬一遍數據庫查找的流程:SQL語句爲select * from table_name where a = 25
,而且a字段創建了索引,那麼查詢的時候就不用遍歷表了,只要查詢紅黑樹,只要通過三次查找(13,17,25)就能夠獲得a=25所在行的指針.有了指針就能夠讀取到一整行的數據.
以上就是索引優化的原理.可是通常狀況下,數據庫中的數據是存放在磁盤中,讀取磁盤中的數據要考慮到磁臂移動花費的時間給查詢帶來的影響,因此數據庫中使用的索引通常不是紅黑樹,而是B樹.雖說採用的數據結構不同,可是其原理都是一致的,因此即便是B樹,咱們仍然能夠按照上面的方式來理解索引查詢優化原理.
MyISAM和InnoDB是MySQL經常使用的兩個存儲引擎,兩個也都是採用B樹做爲索引的數據結構,可是雖然如此,二者仍是有很大區別的.
MyISAM索引中的指針就是指向數據所在地址
當有兩個索引的時候,就分別有兩個B樹,兩個索引的指針都是指向數據所在地址
MyISAM的這種索引方式被稱爲非彙集索引.而在InnoDB中,索引結構跟MyISAM有比較大的區別.
InnoDB中,整個數據表就是按照B樹來存儲着,整個表就是一顆巨大的B樹.整個b樹是按照表的主鍵索引的,因此通常InnoDB必需要求表有一個主鍵,若是沒有的話,InnoDB會隱式的生成一個6個字節的整數型做爲索引.
若是創建了其餘索引的話,那麼其索引的指針不是像MyISAM保存指向數據所在地址,而是保存主鍵值.這意味着InnoDB在使用非主鍵索引的時候須要遍歷兩次索引,一次遍歷索引找到主鍵值,根據主鍵再次遍歷主鍵索引找到數據行.
InnoDB中每一個節點的數據相似:
node { node *left //左子樹指針 node *right //右子樹指針 int index //索引值 Data data //index所在行的一整行數據 }
根據InnoDB索引的特殊性.因此能夠得出一些使用InnoDB存儲引擎的注意方式:
表最好要有一個主鍵.避免MySQL爲咱們隱式生成一個主鍵.
主鍵除了確保惟一性,並且佔用的空間要少,由於非主鍵索引都是保存主鍵值的,若是主鍵爲相似身份證這類數據,那麼會致使索引過大.
主鍵最好有auto_increment,否則再每次插入非單調數據的時候爲了保持B樹的結構會頻繁分裂樹結構致使性能下降
要想高效使用索引,就得知道什麼樣的查詢語句會使用到索引.對於單列索引,只要where字句包含了索引就可使用到索引.可是不少人很容易忽略聯合索引,覺得聯合索引只是單純的幾個單列索引疊加在一塊兒.其實否則,若是可以創建優秀的聯合索引,效率會比創建多個單列索引好上不少.這裏咱們使用MySQL的Explain命令來分析SQL執行的過程,因此在深刻索引以前來看看Explain怎麼使用
使用Explain只要在查詢語句前面加上explain就能夠了.
mysql> explain select * from t1 where a=1 and b=2 and c=3; +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ 1 row in set (0.00 sec)
如上 .執行Explain以後會返回給咱們相似上面的數據.這裏挑幾個咱們下面重點用到的字段.注意,有些沒有提到的字段並非不重要,只是咱們重點在於使用這些字段分析索引.因此能夠略過一些字段.
當值包含using index
的時候說明使用了覆蓋索引.什麼是覆蓋索引,即select要查詢的字段正好就是當作索引的字段.好比上面當查詢爲select a,b,c from t1 where a=1 and b=2 and c=3
.而咱們正好有一個索引idx_abc(a,b,c)
.因此這個查詢就使用到了覆蓋索引.使用覆蓋索引有什麼好處?回憶一下上面索引的原理,查詢的時候查詢到了B樹的某一個節點,要獲得這個節點中保存的主鍵的值,而後再次遍歷主鍵索引才能查詢到數據.若是咱們要查詢的數據就是索引字段,那麼就避免了第二次查找索引.
當值包含Using filesort
說明MySQL在查詢完要的數據以後,還要對數據進行一次排序才能返回結果.
表示當前查詢使用到索引的字節數.經過這個字段咱們能夠分析出在查詢的時候是否有效的使用了聯合索引.那麼如何計算ken_len?,這裏有幾個能夠遵循的法則:
key_len 等於索引列類型字節長度,如int佔據4個字節.
若是索引列爲變長類型的數據須要 +2 字節,這個變長類型包括varchar,以及text等長數據創建的部分索引.
若是索引列容許爲空,那麼須要 +1 字節
字符類型的索引長度跟字符集有關.
根據上面的法則,若是有一個索引a,其字段定義爲a varchar(10)
,而且a保存的是utf-8的數據,那麼:
varchar爲變長數據 須要 +2 字節
字段沒有定義not null
,說明容許爲空, 須要 +1 字節
保存的是utf-8數據,一個字符佔據3字節, 須要 10*3 字節
因此,這個索引的長度爲:2 + 1 + 10*3=33字節
key字段表示當前搜索使用到的索引
possible_能夠表示搜索以前MySQL估計可能使用到的索引
這個字段說明了MySQL是如何查找表中的行.這個字段有以下的可能值,這些值從上到下依次代表了本次查詢從最差到最優.
ALL : 表示MySQL必須掃描整張表才能找到所須要的行.注意這裏的掃描是指從表的第一行數據開始掃描.
index : 跟ALL的同樣 須要掃描全表才能找到須要的行,可是不一樣的是掃描的順序不是從表的第一行開始,而是根據索引的順序掃描的.這個有一點好處是避免了排序,由於索引自己已是有序的.
range : 這種查詢跟index不一樣的地方在於沒必要進行全表掃描這種類型的查詢通常在where字句中帶有<, between
的時候出現.因此這種狀況下,只要掃描部分索引就能夠了.
ref : 這種查找通常在使用複合索引的時候會出現,具體含義在下文給出.
除了這些以外, 還有另外的eq_ref,const,system,null
等可能.因爲下文沒有出現這些可能性,因此就不一一說明了.
好了,懂得根據Explain分析SQL執行過程以後,咱們來看看如何使用複合索引纔是最高效的.
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf-8
有上面的表結構,咱們創建了一個複合索引idx_abc
,其實至關於創建了三個索引:idx(a),idx(a,b),idx(a,b,c)
三個索引.可是在具體查詢的時候,是否可以用上這些索引還須要深刻分析.
mysql> explain select * from t1 where a=1 and b=2 and c=3; +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ 1 row in set (0.00 sec)
當查詢精確匹配索引中的每個字段的時候,就會使用到(a,b,c)索引.因此這裏的key_len等於15,即 4+1+4+1+4+1=15.這裏所謂的精確匹配搜索指的是查詢類型爲 = 或者 in
的時候.
並且這裏要注意一點,MySQL對索引的順序是敏感的,即定義索引的時候順序爲idx_abc(a,b,c)
,那麼查詢時候字句where的出現順序也應該是a,b,c才能夠用到索引.可是因爲MySQL優化器會在查詢以前幫咱們調整順序.因此,即便你查詢寫成select * from t1 where b=1 and a=2 and c=3;
仍然可使用到索引.
mysql> explain select * from t1 where a =2 and c=4; +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where; Using index | +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ 1 row in set (0.00 sec)
這裏where字句少了b字段,發如今查詢時候只能用到索引a,這裏就引出了一個最左前綴原理的概念.若是在查詢的時候只是包含了索引定義順序的某些字段,那麼就只能用到複合索引的一部分.好比上面的查詢,索引的定義順序爲(a,b,c).可是在查詢的時候少了b字段,雖然Explain的key字段說明了使用到idx_abc
索引,可是從ken_len
中發現只是使用到了索引的第一列前綴.即只使用到了a.
當查詢爲:
mysql> explain select * from t1 where a=4 and b=4; +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 10 | const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+
這個時候(a,b)是按照索引定義順序出現的,因此這裏能夠用到(a,b)索引.即key_len=10
根據上面說的,若是查詢的時候沒有按照索引定義的順序來使用索引,那麼只有左邊的部分可使用到索引.如今咱們來看一個查詢.
mysql> explain select * from t1 where b=4; +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ | 1 | SIMPLE | t1 | index | NULL | idx_abc | 15 | NULL | 1 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ 1 row in set (0.00 sec)
這個查詢只使用到了b,並無使用a.按照上面的最左前綴原理,應該是沒辦法使用索引纔對,可是這裏的 ken_len
爲15說明使用到了索引.這個時候咱們須要看type字段,發現這個查詢type=index
.而上一個爲type=ref
.這兩個有什麼區別?
type=index
在解釋Explain的使用的時候有講到這種查詢MySQL會對該索引進行掃描.這種只要where字句後面的字段爲索引,或者複合索引的一部分,那麼MySQL就會以index的方式進行掃描.可是這種掃描是很耗費性能的,由於他要從索引的第一個值開始掃描整張表.並且這種掃描是隨機IO,由於這種掃描不是按照表的順序掃描,而是按照索引的順序掃描.這種查詢的好處在於避免了排序.因此這裏ken_len表示的不是使用索引進行查找數據,而是根據索引順序進行掃描整張表
type=ref
,這種查找就是咱們日常說的使用索引能提升查詢效率的查找,他是查找和掃描的混合體.這樣講可能有點難以理解.上栗子.
有索引idx_ac(a,b,c)
,那麼索引在MySQL內部的構造相似上圖,這有點相似 order by a b c
的感受,A字段是絕對有序的,只有在A字段同樣的狀況下,纔對B進行排序.最後纔是C.
當咱們查詢where a=2 and c=5
的時候,因爲a是絕對有序的,因此查詢時候經過索引能夠立刻查詢到2,3行.以後c=5
因爲沒有了中間的B,因此C至關因而無序的.這個時候,MySQL只能掃描2,3行來找到所須要的數據.這樣子,咱們也就解釋了爲何查詢where a=3 and c=5
的時候只能用到索引a了.可是這個查詢的的確確使用到了索引來加快查詢速度.說着這個查詢的type=ref
.而不是type=index
如今,咱們對上面的表增長一個字段alter table t1 add other int
.這樣子,表的結構就變成
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, `other` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) )
這個時候咱們再執行查詢語句
mysql> explain select * from t1 where c=4; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
發現,這裏type=ALL
,按照上面Explain提到的,這個說明MySQL會去掃描全表,這是效率最低的狀況.但是和上面的查詢相比, 爲何咱們查詢的語句同樣,查詢的方式卻從type=index
變成了type=all
?問題就在於咱們添加了一個字段,添加一個字段以後讓select * from t1 where c=4
變成非覆蓋索引的查詢.在非覆蓋索引查詢的時候,沒有遵循最左前綴原則的查詢,只能掃描全表查詢.
mysql> explain select * from t1 where a=4 and b<5 and c=4; +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ | 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition | +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ 1 row in set (0.00 sec)
上面這種查詢,雖然知足最左前綴,可是中間的B爲範圍查詢(<, between),那麼範圍查詢以後的字段是用不到索引的,因此能夠看出.這裏的ken_len
爲10.其原理跟上面的同樣, 因爲B是範圍查詢,因此至關於C是無序的.這個時候只能使用掃描的方式來查找C.
索引在排序中的做用仍是跟上面的同樣.咱們經過一個例子來分析下.
表結構以下
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, `other` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1
查詢語句爲:
mysql> explain select * from t1 where a=1 order by b; +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where | +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ 1 row in set (0.00 sec)
這裏,咱們where字句只有a=1
一個,因此key_len=5
.可是Extra並無出現using filesort
的字段,說明這裏沒有進行排序.緣由跟上面的相似:MySQL經過索引 查找到a=1
的行數以後,因爲a字段都相等,那麼B字段已是有序的,因此就沒必要再進行排序了.
若是查詢換成:
mysql> explain select * from t1 where a=1 and b <3 order by c; +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ | 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition; Using filesort | +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ 1 row in set (0.00 sec)
這個時候在extra字段多了Using filesort
,說明MySQL查詢到所須要的字段以後還要進行排序,由於這個SQL語句B是範圍查詢,因此C是無序的.