mysql索引優化實踐

背景

最近在寫一些數據統計的面板,裏面有sql對錶數據的聚合統計,我的主表現在有100來萬的數據,其間看了很多資料。記錄一下sql索引的優化過程. sql 如下,只有一個連表查詢,再加上函數聚合出結果

select count(if(b.severity = 1, true, null)) severityAllNum,
        count(if(b.severity = 2, true, null)) importanceAllNum,
        count(if(b.severity = 1 and timestampdiff(minute, b.time, b.solve_time) <= 60, true, null)) severityDoneNum,
        count(if(b.severity = 2 and timestampdiff(minute, b.time, b.solve_time) <= 240, true, null))
        importanceDoneNum
        from (
        select a.time, a.solve_time, a.severity
        from alarm_log a
        left join alarm_log_app_info alai using(alarm_business_id)
        where

            a.channel = 'apppush'
            and a.status in (2, 3, 4)
            and a.time >= '2019-12-30 00:00:00'
            and a.time <= '2020-01-03 23:59:59'
    
        group by alai.alarm_business_id
        order by null
        ) b

我們看一下沒有任何索引這個的執行計劃,表a的rows函數是100多w,type = all,屬於全表掃描了,這樣執行sql會非常慢。我們急需索引來篩選不需要掃描的行。
在這裏插入圖片描述

最左前綴匹配原則

最左前綴匹配原則,建立索引我們需要第一個要想到的,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。 從這裏也建議了最好不要用單列索引

但是如何建立一個多列索引呢,多個列名怎麼做排列?這裏引出 列區分度,簡單的說就是每一列的 所有不重複數據 / 總條數的比例,按照上面的sql,我們用另一條sql計算一下where/select/group by 那些列的區分度

select count(distinct alarm_business_id) / count(*),
       count(distinct time) / count(*),
       count(distinct solve_time) / count(*),
       count(distinct channel) / count(*),
       count(distinct status) / count(*),
       count(distinct severity) / count(*)
from alarm_log;

在這裏插入圖片描述

我們可以看到 alarm_business_id,time 區分度是比較高的。它們可以放在索引的前面,讓mysql引擎層篩選出更多的行,但是最左前綴匹配原則會匹配到範圍查詢爲止,所以我們更多把 時間字段放在索引最後。下面是我的第一版索引

create index alarm_log_index
    on alarm_log (channel, status, severity, alarm_business_id, time, solve_time);

我們看看索引掃描了多少行,58w多,差不多是總數的一半。說明索引建立的還是有效果的,但是我們的索引優化還沒有終結。
在這裏插入圖片描述

覆蓋索引

上圖中extra列中有一個use index,這是啥意思?這裏提到了 覆蓋索引的概念,我們首先要知道,主鍵是一個聚簇索引,它保存了這一行所有列的數據,所以當我們根據id 去查找一條數據時,我們取出來的列數據是從索引中直接拉取出來的,無需回表查詢,而回表的意思就是拿着主鍵id重新從數據庫查一遍我們需要的列數據,而非主鍵列的索引 叫非聚簇索引,它只持有主鍵的id,所以當你用非聚簇索引查詢出來的數據 其實經過了兩次io – 第一次查詢出條件行的id,第二次根據id查詢出所需要的列數據(回表)。

那有個疑問了,非聚簇索引就一定只能回表才能查詢出所有數據嗎? 覆蓋索引 就解決了我們的難題,我們把 where 和 select的列全部加到索引中,並且執行計劃中 extra列 顯示use index,就說明覆蓋索引開始生效。試想,如果索引的葉子結點已經包含了要查詢的數據,我們何必要多做一次回表查詢.

索引下推 index condition pushdown

索引優化沒有一個固定的模板,一切都是根據業務實際嘗試出來的結果,有些時候你認爲mysql應該走這個索引,但是實際上卻走的全表掃描,很大原因是因爲mysql執行計劃認爲你的數據量還不夠大,走索引還不如全表掃描快,換言之,理論上覆蓋索引已經是索引優化的理想態,因爲它全部都是走索引取數據嘛,但是實際情況你可以繼續減少掃描行來提高查詢性能,接下來就介紹索引下推,這應該是mysql 5.6之後出來的特性。

我們來看看 沒有索引下推和有索引下推 ,一條sql是怎麼篩選數據的。
在這裏插入圖片描述
索引下推 貌似優化了 以前最左前綴匹配的侷限,在引擎層可以過濾更多的行,直接的好處就是減少了回表查詢和server層額外的where過濾。基於這個特性,我優化了一下我的索引,把區分度最高的time字段放第一個,這樣引擎層能一下篩選掉大部分數據,而且有了索引下推,能保證後面字段不會因爲範圍查詢而用不上索引。

create index alarm_log_index
    on alarm_log (time, alarm_business_id, solve_time, metric_name, severity, major_type, channel);

可以看到掃描的行數 下降到15w左右,效果顯著!extra列 顯示 use index condition ,說明索引下推開始生效。
在這裏插入圖片描述

鳴謝

這篇文章是我真實的一個sql索引調優過程,但是期間參考了很多資料,也系統學習了下索引調優。要感謝下面的文章

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