在我們使用MySQL的過程中,隨著數據量越來越大,查詢顯得有些吃力,這時候就要針對查詢就行優化,針對查詢優化,通過給字段添加索引可以提高數據的讀取速度,提高項目的并發能力和抗壓能力。在上一篇我們講了給數據庫中的表添加索引,來提高它的查詢速度,但是會有另外一種情況出現,那就是我們給表中字段加了索引,但是查詢的時候依舊很慢,沒有什么變化,這時就是因為建立的索引失效了,今天就來講一講索引失效的情況!
對于查看索引是否失效,MySQL數據庫針對每一條SQL語句,提供了一個查看它的執行計劃的工具,叫做EXPLAIN,咱們先來了解一下這個工具。首先說一下接下來用到的表,創建的表結構為:
CREATE TABLE `tb_user` ( `id` BIGINT (20), `user_name` VARCHAR (200), `user_password` VARCHAR (200), `birth` DATETIME , `sex` CHAR (4), `age` int(8), `email` VARCHAR (200), `mobile` VARCHAR (200), `create_date` DATETIME , `update_date` DATETIME , `description` VARCHAR (800) ) ENGINE=INNODB;
創建一個存儲函數,向里面插入一百萬條數據:
DELIMITER $$ CREATE PROCEDURE insert_tb_user() BEGIN DECLARE i INT; SET i=0; WHILE i>=0 && i<= 1000000 DO INSERT INTO tb_user (`id`, `user_name`, `user_password`, `birth`, `sex`, `age`, `email`, `mobile`, `create_date`, `update_date`, `description`) VALUES (i,concat('admin',i),concat('admin',i),NOW(),'M',i,'[email protected]',concat('12345',i),NOW(),NOW(),concat('超級管理員',i)); SET i=i + 1; END WHILE; END$$ DELIMITER ; -- 執行存儲函數 call insert_tb_user()
生成的表數據如下:

利用這個表,我們來介紹一下執行計劃和索引失效的情況。
首先來看一下執行計劃的結果是什么樣的,執行計劃的查看,就是在查詢語句的前面加上EXPLAIN關鍵字就可以了:

對于這些字段的意思,咱們一一來解釋:
一、 id,SELECT識別符。
*id相同時,執行順序由上至下
*如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
二、select_type,查詢中每個select子句的類型
*SIMPLE(簡單SELECT,不使用UNION或子查詢等)
*PRIMARY(子查詢中最外層查詢)
*UNION(UNION中的第二個或后面的SELECT語句)
*DEPENDENT UNION(UNION中的第二個或后面的SELECT語句)
*UNION RESULT(UNION的結果,union語句中第二個select開始后面所有select)
*SUBQUERY(子查詢中的第一個SELECT,結果不依賴于外部查詢)
*DEPENDENT SUBQUERY(子查詢中的第一個SELECT,依賴于外部查詢)
* DERIVED(派生表的SELECT, FROM子句的子查詢)
* UNCACHEABLE SUBQUERY(一個子查詢的結果不能被緩存,必須重新評估外鏈接的第一行)
三、 table,顯示這一步所訪問數據庫中表名稱。
四、type,對表訪問方式
*all:Full Table Scan, MySQL將遍歷全表以找到匹配的行
*index: Full Index Scan,index與ALL區別為index類型只遍歷索引樹
*range:只檢索給定范圍的行,使用一個索引來選擇行
*ref: 表示上述表的連接匹配條件,即哪些列或常量被用于查找索引列上的值
*eq_ref: 類似ref,區別就在使用的索引是唯一索引,對于每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連接中使用primary key或者 unique key作為關聯條件
*const、system: 當MySQL對查詢某部分進行優化,并轉換為一個常量時,使用這些類型訪問。如將主鍵置于where列表中,MySQL就能將該查詢轉換為一個常量,system是const類型的特例,當查詢的表只有一行的情況下,使用system
*NULL: MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列里選取最小值可以通過單獨索引查找完成。
五、 possible_keys,可能使用的索引。
六、key,實際使用的索引
七、key_len,索引中使用的字節數,可通過該列計算查詢中使用的索引的長度
八、ref,列與索引的比較,表示上述表的連接匹配條件
九、rows,估算出結果集行數
十、Extra,MySQL解決查詢的詳細信息
*Using where:不用讀取表中所有信息,僅通過索引就可以獲取所需數據
*Using temporary:表示MySQL需要使用臨時表來存儲結果集,group by ; order by
*Using filesort:包含 order by ,而且無法利用索引完成的排序操作稱為“文件排序”
*Using join buffer:該值強調了在獲取連接條件時沒有使用索引,并且需要連接緩沖區來存儲中間結果。
*Impossible where:這個值強調了where語句會導致沒有符合條件的行。
*Select tables optimized away:這個值意味著僅通過使用索引,優化器可能僅從聚合函數結果中返回一行
*No tables used:Query語句中使用from dual 或不含任何from子句
以上就是對EXPLAIN工具的一個介紹,了解了這個工具后,我們結合這個執行工具,來看一看哪些情況會導致這個索引失效!
第一種情況:針對聯合索引,是否遵循最左匹配原則;
我們user_name,user_password,mobile建立一個聯合索引,如下:

聯合索引其實是建立了三個索引,也就是user_name的索引,user_name,user_password的索引,user_name,user_password,mobile的索引,最左匹配原則的意思,是否在語句中使用了最左建立的索引,也就是user_name的索引,演示一下:

當我們把user_name的查詢條件去掉之后,會是什么情況呢?

我們會看到索引已經失效了,用的是全表掃描,違背了最左匹配的原則,那么對于查詢語句select * from tb_user where user_password='admin66666' and user_name='admin66666' and mobile='1234566666'會用到索引嗎?答案如下:

也是用到了聯合索引,這和你條件中寫的順序是沒有關系的!
第二種情況:在索引列上做了函數操作,會導致索引失效而導致全表掃描
我們先把那個聯合索引刪除掉,然后在user_name這一列上建立一個唯一索引:



先不在索引列上做函數操作,執行計劃如下:

很顯然使用了索引,那么索引列進行函數操作呢?例如做一個字符拼接的操作:

第三種情況:語句中like查詢是以%開頭,索引會失效變成全表掃描,覆蓋索引。
示例如下:

如果模糊查詢不是以%開頭的,那么也是可以用到索引的:

第四種情況:使用is not null 會導致無法使用索引
示例如下:

第五種情況:查詢語句中,如果條件中有or,即使其中有條件帶索引也不會使用。要想使用or,又想讓索引生效,只能將or條件中的每個列都加上索引

上面的sql語句是可以用到索引的,當我們把and換成or時,就會變成全表掃描:

這時我們對mobile也加上索引,這條sql語句也就會使用上索引:


第六種情況:使用不等于(!= 或者<>)的時候,無法使用索引,會導致索引失效
第七種情況:不能使用索引中范圍條件右邊的列,范圍之后索引失效。(< ,> between and)
這些情況就不在進行實際操作了,感興趣的朋友可以動手操作一下,也許隨著MySQL版本的更新迭代,對這些查詢語句進行內部優化,一些索引失效的情況就會消失。除了以上這些情況會導致索引失效,還有哪些情況會導致索引失效呢?