mysql判斷sql語句是不是慢查詢,是根據語句的執行時間來衡量的,mysql會用語句的執行時間和long_query_time這個系統參數做比較,如果語句執行時間大於long_query_time,都會把這個語句記錄到慢查詢日志里面。long_query_time的默認值是10s,一般生產環境不會設置這么大的值,一般設置1秒。
語句是否用到索引,是指語句在執行的時候有沒有用到表的索引。圖一:未用到索引,圖二:使用主鍵索引,圖三:使用了“a”索引。
圖二:如果數據的壓力非常高,那么該sql執行的時間也有可能超過long_query_time,會記錄到慢查詢日期里面
圖三:是掃描了整個“a”的索引樹,如果數據大於100W行效率就會變慢
是否執行索引只是表示了一個SQL語句執行的過程,而是否記錄慢查詢,是有執行時間決定的,而這個執行時間,可能會受外部因素的影響,也就是說是否使用索引,和是否記錄慢查詢之間並沒有必然的聯系。
什么叫做使用了索引,innoDB是索引組織表,所有的數據都是存儲在索引樹上面的。如下圖所示,該表建立了兩個索引,一個主鍵索引,一個普通索引a,在innoDB里數據是放在主鍵索引里的。
數據索引示意圖:
如果執行explanin select * from t where id > 0 如下圖:但是從數據上這個sql一定是做了全表掃描,但是優化器認為,這個sql的執行過程中需要根據主鍵索引定位到第一個滿足id>0的值。即便這個sql使用到了索引,實際上也可能是全表掃描。
因此innoDB只有一種情況沒有使用到索引,就是從主鍵索引的最左邊的葉子節點開始,向右掃描整個索引樹,也就是說沒有使用索引並不是一個准確的描述,你可以用全表掃描表示一個查詢遍歷了整個主鍵索引樹,也可以用全索引掃描說明像 select a from t這樣的查詢,它掃描了整個普通的索引樹,而像select * from t where id=2 這樣的語句,才是我們平時說的使用了索引,它表示的意思是,我們使用了索引的快速搜索功能,並且有效的減少了掃描行數。
索引的過濾性
假設現在維護了一張記錄了整個中國人的基本信息表,假設你要查詢所有年齡在10到15歲之間的基本信息,通常語句就會是:select * from t_people where age between 10 and 15; 一般都會在age這個字段增加一個索引,否則就是一個全表掃描,但是在建了age上的索引后,這個語句還是執行慢,因為滿足這個條件的數據有超過1億行。建立索引表的組織結構圖如下: 那么上面的sql語句執行流程是,從索引age上用樹搜索,取出第一個age=10的記錄,得到它的主鍵ID的值,根據ID值去主鍵索引樹取整行的信息,作為結果集的一部分返回,在索引age上向右掃描,取出下一個ID值,到主鍵索引上取出整行信息,作為結果集的一部分返回。重復改操作,只到碰到第一個age > 15的記錄。
其實最終關系的是掃描行數,對於一個大表,不止要有索引,索引的過濾性也要足夠好,像剛才的例子age這個索引,它的過濾性就不夠好,在設計表結構的時候,我們要讓索引的過濾性足夠好,也就是區分度比較高,那么過濾性好了,是不是標識查詢的掃描行數就一定少呢?在看一個例子,參考下圖:
如果有一個索引是姓名、年齡的聯合索引,那這個聯合索引的過濾性應該不錯,如果你的執行語句是:select * from t_people where name ='張三' and age = 8; 就可以在聯合索引上快速找到第一個姓名是張三,並且年齡是8的小朋友,這樣的數據應該不會很多,因此向右掃描的行數也很少,查詢效率就很高,但是在查詢的過濾性和索引的過濾性不一定是一樣的,如果現在你的需求是查出所有名字第一個字是張,並且年齡是8的所有小朋友,SQL語句通常這樣寫:select * from t_people where name like '張%' and age = 8;
在mysql5.5之前的版本中,這個語句的執行流程是這樣的 (參考下圖)從聯合索引樹上找到第一個姓名字段上第一個姓張的記錄,取出主鍵ID,然后到主鍵索引上,根據ID取出
整行的值,判斷年齡是否等於8,如果是就做為結果集的一行返回,如果不是就丟棄。我們把根據ID到主鍵索引上查找整行數據的這個動作,叫做回表,在聯合索引上向右遍歷,並重復做回表和判斷的邏輯。直到碰到聯合索引樹上,第一個姓名第一個字不是張的記錄為止。可以看到這個執行過程里面,最耗時的步驟就是回表。假設全國名字第一個字姓張的人有8000W,那么這個該過程就回表8000W次。在定位第一行記錄的時候,只能使用索引和聯合索引的最左前綴,稱為最左前綴原則。可以看到這個執行過程它的回表次數特別多,性能不夠好,有沒有優化的方法呢?
在Mysql5.6版本引入了index condition pushdown的優化。優化的執行流程是:從聯合索引樹上找到第一個年齡字段是張開頭的記錄,判斷這個索引記錄上的年齡值是不是8,如果是就回表,取出整行數據,做為結果集返回的一部分,如果不是就就丟棄,不需要回表,在聯合索引樹上向右遍歷,並判斷年齡字段后,根據需要做回表,知道碰到聯合索引樹上,名字的第一個字不是張的記錄為止。這個過程跟上面的過程的差別,是在遍歷聯合索引的過程中,將age=8這個條件下推到索引遍歷的過程中,減少了回表次數。假設全國名字第一個字是張的人里面,有100W個年齡是8的小朋友,那么這個查詢過程中,在聯合索引里要遍歷8000W次,而回表只需要100w次。可以看到index condition pushdown優化的效果還是很不錯的,但是這個優化還是沒有繞開最左前綴原則的限制,因此在聯合索引里,還是要掃描8000W行,有沒有更進一步的優化呢?
虛擬列的優化方式
可以把名字的第一個字,和年齡做一個聯合索引來試試,可以使用mysql5.7引入的虛擬列來實現,對應的修改表結構的sql語句是這么寫的:
alert table t_people add name_first varchar(2) generated always as (left(name,1)),add index(name_first,age);
虛擬列的值,總是等於name字段的前兩個字節,虛擬列在插入數據的時候,不能指定值,在更新的時候也不能主動修改,它的值會根據定義自動生成,在那么字段修改的時候,也會自動跟着修改。有了這個新的聯合索引,我們再找名字的第一個字是張,並且年齡是8的小朋友的時候,這個SQL語句就可以這么寫:
select * from t_people where name_first='張' and age=8;
這個SQL語句執行的過程,就只需要掃描聯合索引的100W行,並回表100W次,這個優化的本質是我么創建了一個更緊湊的索引,來加速了查詢的過程。
使用sql優化的過程,往往就是減少掃描行數的過程