面試三輪我倒在了一道sql題上——sql效能優化

牧小農的夏天發表於2020-06-04

一、前言

最近小農在找工作,因為今年疫情的特殊原因,導致工作不是特別好找,所以一旦有面試電話,如果可以,都會去試一試,剛好接到一個面試邀請,感覺公司還不錯,於是就確定了面試時間,準備了一下就去面試了。

第一輪面試是小組組長面試,通過。
第二輪是經理面試也是通過了。
第三輪總監面試,前面都還有模有樣,突然畫風一轉,面試官說:“問你最後一個問題”

面試官:10W條資料,我要從其中查出100條不連續的資料,給你id,來查name和password進行展示,如何才能高效能的去使用?

我:在id上建立聚簇索引,然後用 in id 來縮小表搜尋範圍,最後 使用條件查詢 小於最大id,大於最小id,這樣可以讓sql速度能夠比較快的展示,雖然In的效能比較低
心裡活動:雕蟲小技,還最後一個問題,這樣的問題再來一個吧

只見面試官緊鎖眉頭,與我心裡期待的表情有點不一樣啊,難道是哪個環節出了問題?
面試官:這樣的效能不能達到最優化的程度,而且如果我給你的最小id是1,最大id是100000呢?

你這就有點槓精了啊,那行吧,你是面試官你說了算
我:既然id已經給出來了,而且只查詢兩個欄位,用聚簇索引那麼查詢資料是很快的,用in id應該是可以的。

面試官:好的,回去等通知吧
我。。。。。

二、後知

於是回去後,查詢資料,才知道原來面試官,真正想考的是 “覆蓋索引”

什麼是覆蓋索引:

當sql語句的所求查詢欄位(select列)和查詢條件欄位(where子句)全都包含在一個索引中 (聯合索引),可以直接使用索引查詢而不需要回表。這就是覆蓋索引,通過使用覆蓋索引,可以減少搜尋樹的次數,這就是 覆蓋索引,在瞭解覆蓋索引之前,我們先來看看什麼是索引。

三、什麼是索引?

我們有一個主鍵列為id的表,表中有欄位name,並且在name上有索引

表中 t_user 值分別為(1,張一)、(2,張二)、(3,張三)、(4,張四)、(5,張五)

表結構如下:

mysql> create table t_user (
id bigint(20) not null auto_increment ,
name varchar(255) not null,
primary key (id),
index index_name (name) using btree)
engine=innodb
default character set=utf8 collate=utf8_general_ci

兩棵樹的示例示意圖如下:
在這裡插入圖片描述

從圖中不難看出,根據葉子節點的內容,索引型別分為主鍵索引和二級索引(非主鍵索引)。

主鍵索引: 主鍵索引的葉子節點儲存著主鍵即對應行的全部資料。在InnoDB裡,主鍵索引也被稱為聚簇索引(clustered index)。

二級索引(非主鍵索引): 二級索引樹中的葉子結點儲存著索引值和主鍵值,當使用二級索引進行查詢時,需要進行回表操作。在InnoDB裡,非主鍵索引也被稱為二級索引(secondary index)

通過上面所講的,我們來看看如何通過sql語句來區分 主鍵索引和普通索引的查詢

  • select * from t_user where id=1 即主鍵查詢方式,則只需要搜尋id這棵B+樹
  • select * from t_user where name=張三 即普通索引查詢方式,則需要先搜尋name索引樹,得到id的值為3,再到id索引樹搜尋一次。這個過程稱為回表

也就是說,基於二級索引(非主鍵索引)的查詢需要多掃描一棵索引樹。因此,我們在應用中應該儘量使用主鍵查詢。

看到這裡如果你看懂了上面的介紹,那麼這裡你會有一個疑問,我直接用in id不就好了嗎,建立id主鍵索引,就可以不用回表了,速度不也就提升了嗎?

如果是 5.5 之前的版本確實不會走索引的,在 5.5 之後的版本,MySQL 做了優化。MySQL 在 2010 年釋出 5.5 版本中,優化器對 in 操作符可以自動完成優化,針對建立了索引的列可以使用索引,沒有索引的列還是會走全表掃描,也就是我們所說的回表。

那麼,有沒有可能經過索引優化,避免回表過程呢?答應是有的

四、覆蓋索引

sql語句如下,其中id自增,name為索引:

mysql> create table t_user (
id bigint(20) not null auto_increment ,
name varchar(255) not null,
password varchar(255) ,
primary key (id),
engine=innodb
default character set=utf8 collate=utf8_general_ci

比如有這麼兩句sql

語句A: select id from user_table where name= '張三'
語句B: select password from user_table where name= '張三'

語句A: 因為 name索引樹 的葉子結點上儲存有 name和id的值 ,所以通過 name索引樹 查詢到id後,因此可以直接提供查詢結果,不需要回表,也就是說,在這個查詢裡面,索引name 已經 “覆蓋了” 我們的查詢需求,我們稱為 覆蓋索引

語句B: name索引樹 上 找到 name='張三' 對應的主鍵id, 通過回表在主鍵索引樹上找到滿足條件的資料

因此我們可以得知,當sql語句的所求查詢欄位(select列)和查詢條件欄位(where子句)全都包含在一個索引中(聯合索引),可以直接使用索引查詢而不需要回表。這就是覆蓋索引

例如上面的語句B是一個高頻查詢的語句,我們可以建立(name,password)的聯合索引,這樣,查詢的時候就不需要再去回表操作了,可以提高查詢效率。

所以關於上面的面試題我們就可以得出,使用聯合索引就可以很好的回答面試官的問題(id,name,password)這樣的聯合索引就可以呼叫到覆蓋索引,可以減少樹的搜尋次數,不再需要回表查整行記錄,顯著提升查詢效能,所以使用覆蓋索引是一個常用的效能優化手段。

說到了聯合索引我們就不得不說聯合索引中最重要的匹配原則,最左匹配原則了

五、最左匹配原則

最左字首匹配原則,是非常重要的原則,mysql會從左向右進行匹配。

例如我們定義了(name,password)兩個聯合索引欄位,我們 使用 where name = '張三' and password = '2' 索引可以生效的,當我們是顛倒了他們的順序 使用where password = '1' and name = '王五',索引同樣也是可以生效的,在mysql查詢優化器會判斷糾正這條sql語句該以什麼樣的順序執行效率最高,最後才生成真正的執行計劃,我們能儘量的利用到索引時的查詢順序效率最高,所以mysql查詢優化器會最終以這種順序(where name = '張三' and password = '2' )進行查詢執行,就類似 我們的 order by name,password這樣一種排序規則,先對張三的使用者進行查詢排序,在對password進行處理

在這裡插入圖片描述

比如我們要查詢姓張的使用者,我們的條件查詢可以為 "where name like ‘張%’",但是不能是 where name like '%張%'或者是 where name like '%張',因為索引可以用於查詢條件欄位為索引欄位,根據欄位值必須是最左若干個字元進行的模糊查詢,也就是需要是 '張%' 這樣的新增才可以使用。

索引的複用能力。因為可以支援最左字首,所以當已經有了(name,password)這個聯合索引後,一般就不需要單獨在name上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護一個索引,那麼這個順序往往就是需要優先考慮採用的。

如果既有聯合查詢,又有基於name,password各自的查詢呢?查詢條件裡面只有password的語句,是無法使用(name,password)這個聯合索引的,這時候你需要同時維護(name,password)、(password) 這兩個索引。

建立索引時,我們也要考慮空間代價,使用較少的空間來建立索引
假設我們現在不需要通過name查詢password了,需要通過name查詢age或通過age查詢name

  • 1.(name,age)聯合索引+age單欄位索引
  • 2.(age,name)聯合索引+name單欄位索引

name欄位是比age欄位大的,所以,選擇第一種,索引佔用空間較小的一個

六、索引下推

上面我們說到滿足最左字首原則的時候,最左字首可以用於在索引中定位記錄。那麼如果那些不符合最左字首的部分,會怎麼樣呢?

如果現在有一個需求:檢索出表中“名字第一個字是張,而且沒有刪除的資訊(is_del = 1)。SQL語句如下:

mysql> select * from t_user where name like '張%' and is_del=1

在MySQL 5.6之前,只能從匹配的位置一個個回表。到主鍵索引上找出資料行,再對比欄位值

在MySQL 5.6中 引入的索引下推優化(index condition pushdown), 可以在索引遍歷過程中,對索引中包含的欄位先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數

根據(username,is_del)聯合索引查詢所有滿足名稱以“張”開頭的索引,然後回表查詢出相應的全行資料,然後再篩選出未刪除的使用者資料。過程如下圖:

每一個虛線箭頭表示回表一次
圖一(無索引下推執行流程)
圖一

每一個虛線箭頭表示回表一次
圖二(索引下推執行流程)在這裡插入圖片描述

圖1跟圖2的區別是,InnoDB在(name,is_del)索引內部就判斷了資料是否邏輯刪除,對於邏輯刪除的記錄,直接判斷並跳過。在我們的這個例子中,只需要對ID1、ID4這兩條記錄回表取資料判斷,就只需要回表2次

mysql預設啟用索引下推,我們也可以通過修改系統變數optimizer_switch的index_condition_pushdown標誌來控制SET optimizer_switch = 'index_condition_pushdown=off';

我們也需要注意:

  • innodb引擎的表,索引下推只能用於二級索引,因為innodb的主鍵索引樹葉子結點上儲存的是全行資料,所以這個時候索引下推並不會起到減少查詢全行資料的效果
  • 索引下推一般可用於所求查詢欄位(select列)不是/不全是聯合索引的欄位,查詢條件為多條件查詢且查詢條件子句(where/order by)欄位全是聯合索引

六、小結

今天的內容就到這裡了,我們在上面描述了資料庫索引的概念,包括了覆蓋索引、聯合索引、索引下推,那麼下次如果有面試官問你剛開始的問題,相信大家可以好好的回(dui)答(ta)一下面試官了,在sql優化中,減少回表次數,或者直接使用覆蓋索引是比較重要的,儘量少地訪問資源也是資料庫設計的重要原則之一,謝謝大家,加油~

相關文章