為什麼SQL語句使用了索引,但卻還是慢查詢?

2022-07-25 04:24:07 字數 3613 閱讀 8110

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優化的過程,往往就是減少掃瞄行數的過程

為什麼使用索引

資料庫物件索引其實與書的目錄非常相似,主要是為了提高從表中檢索資料的速度。由於資料儲存在資料庫表中,所以索引是建立在資料庫表物件上的,由表中的乙個欄位或多個字段生成的鍵組成,這些儲存在資料結構 b 樹或雜湊表 中,通過mysql可以快速有效的查詢與鍵值相關聯的字段。根據索引的儲存型別,可以將索引分為...

為什麼SQL語句加 1 1

是為了鏈結下面的查詢條件條件,也或者是替換沒有查詢條件的語句。比如 要把檢索條件作為乙個引數傳遞給sql,那麼,當這個檢索語句不存在的話就可以給它賦值為1 1.這樣就避免了sql出錯,也就可以把加條件的sql和不加條件的sql合二為一。這個就是為了方便sql條件的拼接,在 where 和 and 的...

為什麼SQL語句加 1 1

是為了鏈結下面的查詢條件條件,也或者是替換沒有查詢條件的語句。比如 要把檢索條件作為乙個引數傳遞給sql,那麼,當這個檢索語句不存在的話就可以給它賦值為1 1.這樣就避免了sql出錯,也就可以把加條件的sql和不加條件的sql合二為一。這個就是為了方便sql條件的拼接,在 where 和 and 的...