工作中的點點滴滴-MySql的索引失效問題

  最近的工作內容比較枯燥,就是根據運營小姐姐的需求,給她出一些不同維度的數據報表,那麼提到報表,多多少少是離不開數據庫寫sql的,然後就是各種Left Join 呀,Inner Join 呀,子查詢呀。然後在這個過程中,避免不了條件過濾的情況,當數據表的數據量大了起來,那執行一個sql可真的是要了我的老命了。所以這個時候你就要想着怎麼去優化這個sql語句了,所以創建添加索引就標的必不可少了。

  首先在創建索引的前提是,你應該在哪些字段上去創建索引呢?那說到這裏,肯定是需要針對一些需要條件查詢的字段去創建索引呀,其實這句話只說對了一半吧。我們在建立索引的時候,首先要pass掉那種頻繁修改的字段,因爲你在給字段創建索引的時候,其實本質上是在B+樹上創建了一個子葉節點,在你更新索引字段的時候,B+樹會重建索引,這個過程是非常慢的,並且會伴隨着鎖表的情況。其次就是區分度不大的那種字段,比如性別這類,因爲這種類型的字段建索引意義不大,性能基本上和全表掃描的性能差不多,另外就是mysql的優化器有個默認配置,就是返回數據的比例在30%以外的情況,是不會選擇使用索引的,這個30%是一個大概的範圍,並不是固定死的。還有一種比較特數據的,就是會有null的這種字段也不合適做索引,雖然說索引是支持null的,但是從規範上講,null是一種沒有意義的對象,可以設置一個默認值來解決這種問題。索引建立好了,並不是放在where後面跟上查詢條件就可以使用了,畢竟自己也踩過坑了,要不然也不會有這個筆記的誕生了。

  業務表結構和索引展示如下:

 

           

  這張數據操作表的原始數據了大概是730w左右,通過數據id來查詢的sql的執行計劃查看如下:

 

來,我們分析一下這個執行計劃先,要看懂執行計劃,首先我們要搞懂這每一列的含義。

  select_type:查詢類型主要有三種,第一種SIMPLE簡單查詢,這種是最優的查詢語句。第二種是PRIMARY子查詢,一般是最外層會被標記爲PRIMARY。第三種就是聯合查詢UNION了,一般像Left Join,Inner Join這類查詢的。table:查詢涉及到的表或者別名。partitions:分區信息,一般爲null。type:這一列是我們需要重點照看的字段了,這列是訪問類型,也是優化的重點對象,一般這列的結果值從好到不好依次是:system(系統表,少量數據,往往不需要進行磁盤IO)> const (常量連接)> eq_ref (主鍵索引(primary key)或者非空唯一索引(unique not null)等值掃描)> ref (非主鍵非唯一索引等值掃描)> range(範圍掃描) > index(索引樹掃描) > ALL(全表掃描(full table scan)),所以我們常說的sql優化最先就是按照這個指標來優化的。possible_keys:查詢可能會用到的索引,這個字段和後面的key有點兒類似。key:執行計劃實際上使用到的索引,沒有的話就是null。rows:查詢結果大致估算出找到所需的記錄所需要讀取的行數,這個值是越小越好。filtered:和rows類型,所需要查詢到的結果行佔用讀取行的百分比,這個值越大越好。Extra:這列比較特殊,再用到特殊的查詢會體現出來,經常用到的有這些值,可以在優化sql的時候考慮進去,using temporary,使用臨時表保存中間結果,比如在orderBy或者groupBy的時候。using index,查詢語句中使用了覆蓋索引-Covering Index時候,效率會比較好,避免訪問了數據行。using where使用了where條件的時候。using index condition:雖然命中了索引,但不是所有列都在索引樹上需要訪問實際的記錄行。using join buffer:採用關聯查詢或者子查詢的時候需要進行嵌套循環計算。

  現在我們明白了執行計劃了,那麼看索引有沒有生效,這樣就會方便很多了。一:最典型的也是我們經常使用到的不等於<>,type列是All,等於是走了全表掃描。

 

二:列和列相等去做條件查詢,比如operate_date_id = operate_user_id,可以看到這種查詢也是全表掃描的。

 

通過where in 條件查詢,這種查詢條件也比較特殊,比如operate_date_id是int類型,然後operate_date_id in ('111')這種是不會走索引的,但是operate_date_id in (111) 卻是會走索引的,另外如果where in()的條件多了,也是不會走索引的,所以在使用where in的時候,一定要注意。並且像 not,not in,not exists也都是屬於這類似的情況。

四:like通配符來模糊查詢,這種查詢也是比較坑,稍不留神就掉坑裏了,看下面兩個執行計劃。當使用模糊搜索時,儘量採用後置的通配符,後匹配可以走INDEX RANGE SCAN。

另外其他的經常我們會在where條件用upper(operate_user_name)='小旭旭寶寶A',這種函數表達式,這樣也是會導致索引失效。還有一種比較特殊的 大於小於號和between,通過執行計劃你可以發現其實這兩種條件的執行計劃是一抹一樣的。但是呢如果你的mysql版本是>=5.6,在information_schema中的optimizer_trace表,可以跟蹤到執行計劃的具體步驟,通知cost_for_plan執行計劃代價指標來判斷,返現大於小於這種情況的代價值往往會比between小(個人感覺不會是絕對的,雖然通過上面的業務表測試了幾個類型的字段結果都是一致的,但是官方文檔並沒有明文支出這兩種方法的差異),類似於這種大於小於比較符號,通常優化器,會更具實際查詢數據量的比例來判斷,如果全表掃描比索引快,則不會走索引。

  那麼說了索引失效的情況,那具體說爲什麼索引會失效呢?首先先確認一點就是mysql的索引是以B+樹來存儲的,那爲什麼選擇B+樹,而不是哈希索引或者B樹,二叉樹呢?對於哈希來說他是無序的,不能進行範圍搜索。B樹相對於二叉樹,雖然可以解決一部分的查找效率,但是都會有迴旋查找的問題,而B+樹因爲他的非葉子節點可以存儲key,葉子節點既能存儲key也能存儲value,並且葉子節點還是有序的,節點之間用指針連接也避免了迴旋問題了。如果理解了索引的存儲方式後,其實索引查詢就和在B+樹查詢原理是一樣的,如果要命中索引,那麼首先要確定查詢字段的值,也就是查詢字段是在哪些節點上,然後在節點上在通過順序查詢或者二分查詢具體的節點就可以命中索引,反之則會無法命中索引導致索引失效。

  

 

 

          

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