阿里一面,給了幾條SQL,問需要執行幾次樹搜尋操作?
阿新 • • 發佈:2021-01-31
### 前言
有位朋友去阿里面試,他說面試官給了幾條查詢SQL,問:需要執行幾次樹搜尋操作?我朋友當時是有點懵的,後來冷靜思考,才發現就是考索引的幾個基礎知識點~~ 本文我們分九個索引知識點,一起來探討一下。如果有不正確的話,歡迎指出哈,一起學習~
公眾號:**撿田螺的小男孩**
- 面試官考點之索引是什麼?
- 面試官考點之索引型別
- 面試官考點之為什麼選擇B+樹作索引結構
- 面試官考點之一次索引搜尋過程
- 面試官考點之覆蓋索引
- 面試官考點之索引失效場景
- 面試官考點之最左字首
- 面試官考點之索引下推
- 面試官考點之大表新增索引
### 一、面試官考點之索引是什麼?
![](https://imgkr2.cn-bj.ufileos.com/fad79a1f-41c4-4666-87c3-5e0361b67193.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=wYRueM39oR4yjJiLkoAdwMuHc5U%253D&Expires=1612155056)
- 索引是一種能提高資料庫查詢效率的資料結構。它可以比作一本字典的目錄,可以幫你快速找到對應的記錄。
- 索引一般儲存在磁碟的檔案中,它是佔用物理空間的。
- 正所謂水能載舟,也能覆舟。適當的索引能提高查詢效率,過多的索引會影響資料庫表的插入和更新功能。
### 二、索引有哪些型別型別
![](https://static01.imgkr.com/temp/828d9259d5864cfea3c3c7cf8751a052.png)
#### 資料結構維度
- B+樹索引:所有資料儲存在葉子節點,複雜度為O(logn),適合範圍查詢。
- 雜湊索引: 適合等值查詢,檢索效率高,一次到位。
- 全文索引:MyISAM和InnoDB中都支援使用全文索引,一般在文字型別char,text,varchar型別上建立。
- R-Tree索引: 用來對GIS資料型別建立SPATIAL索引
#### 物理儲存維度
- 聚集索引:聚集索引就是以主鍵建立的索引,在葉子節點儲存的是表中的資料。
- 非聚集索引:非聚集索引就是以非主鍵建立的索引,在葉子節點儲存的是主鍵和索引列。
#### 邏輯維度
- 主鍵索引:一種特殊的唯一索引,不允許有空值。
- 普通索引:MySQL中基本索引型別,允許空值和重複值。
- 聯合索引:多個欄位建立的索引,使用時遵循最左字首原則。
- 唯一索引:索引列中的值必須是唯一的,但是允許為空值。
- 空間索引:MySQL5.7之後支援空間索引,在空間索引這方面遵循OpenGIS幾何資料模型規則。
### 三、面試官考點之為什麼選擇B+樹作為索引結構
可以從這幾個維度去看這個問題,查詢是否夠快,效率是否穩定,儲存資料多少,以及查詢磁碟次數等等。為什麼不是雜湊結構?為什麼不是二叉樹,為什麼不是平衡二叉樹,為什麼不是B樹,而偏偏是B+樹呢?
我們寫業務SQL查詢時,大多數情況下,都是範圍查詢的,如下SQL
```
select * from employee where age between 18 and 28;
```
#### 為什麼不使用雜湊結構?
我們知道雜湊結構,類似k-v結構,也就是,key和value是一對一關係。它用於**等值查詢**還可以,但是範圍查詢它是無能為力的哦。
#### 為什麼不使用二叉樹呢?
先回憶下二叉樹相關知識啦~ 所謂**二叉樹,特點如下:**
- 每個結點最多兩個子樹,分別稱為左子樹和右子樹。
- 左子節點的值小於當前節點的值,當前節點值小於右子節點值
- 頂端的節點稱為根節點,沒有子節點的節點值稱為葉子節點。
我們腦海中,很容易就浮現出這種二叉樹結構圖:
![](https://static01.imgkr.com/temp/65e8d067534541b1ac059add7879a99a.png)
但是呢,有些特殊二叉樹,它可能這樣的哦:
![](https://imgkr2.cn-bj.ufileos.com/c4bbbad1-0518-4849-9201-1dbc1a583df7.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=emAbU6n0GC7mQmZDdQdNMzp6x48%253D&Expires=1612155268)
如果二叉樹特殊化為一個連結串列,相當於全表掃描。那麼還要索引幹嘛呀?因此,一般二叉樹不適合作為索引結構。
#### 為什麼不使用平衡二叉樹呢?
平衡二叉樹特點:它也是一顆二叉查詢樹,任何節點的兩個子樹高度最大差為1。所以就不會出現特殊化一個連結串列的情況啦。
![](https://static01.imgkr.com/temp/65e8d067534541b1ac059add7879a99a.png)
但是呢:
- 平衡二叉樹插入或者更新是,需要左旋右旋維持平衡,維護代價大
- 如果數量多的話,樹的高度會很高。因為資料是存在磁碟的,以它作為索引結構,每次從磁碟讀取一個節點,操作IO的次數就多啦。
#### 為什麼不使用B樹呢?
資料量大的話,平衡二叉樹的高度會很高,會增加IO嘛。那為什麼不選擇同樣資料量,**高度更矮的B樹**呢?
![](https://imgkr2.cn-bj.ufileos.com/987bce71-5ec2-4ca1-ab1c-5dfee4474362.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=it31QgnZ6d6tBreqLVmck6fqF%252FA%253D&Expires=1612155373)
B樹相對於平衡二叉樹,就可以儲存更多的資料,高度更低。但是最後為甚選擇B+樹呢?因為B+樹是B樹的升級版:
> - B+樹非葉子節點上是不儲存資料的,僅儲存鍵值,而B樹節點中不僅儲存鍵值,也會儲存資料。innodb中頁的預設大小是16KB,如果不儲存資料,那麼就會儲存更多的鍵值,相應的樹的階數(節點的子節點樹)就會更大,樹就會更矮更胖,如此一來我們查詢資料進行磁碟的IO次數有會再次減少,資料查詢的效率也會更快。
> - B+樹索引的所有資料均儲存在葉子節點,而且資料是按照順序排列的,連結串列連著的。那麼B+樹使得範圍查詢,排序查詢,分組查詢以及去重查詢變得異常簡單。
### 四、面試官考點之一次B+樹索引搜尋過程
**面試官:** 假設有以下表結構,並且有這幾條資料
```
CREATE TABLE `employee` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`sex` int(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into employee values(100,'小倫',43,'2021-01-20','0');
insert into employee values(200,'俊傑',48,'2021-01-21','0');
insert into employee values(300,'紫琪',36,'2020-01-21','1');
insert into employee values(400,'立紅',32,'2020-01-21','0');
insert into employee values(500,'易迅',37,'2020-01-21','1');
insert into employee values(600,'小軍',49,'2021-01-21','0');
insert into employee values(700,'小燕',28,'2021-01-21','1');
```
**面試官:** 如果執行以下的查詢SQL,需要執行幾次的樹搜尋操作?可以畫下對應的索引結構圖~
```
select * from Temployee where age=32;
```
**解析:** 其實這個,面試官就是考察候選人是否熟悉B+樹索引結構圖。可以像醬紫回答~
- 先畫出`idx_age`索引的索引結構圖,大概如下:
![](https://imgkr2.cn-bj.ufileos.com/73c5e262-efd6-4192-91d3-1391691b09d4.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=ntgKw%252BEfi3weXWTvJ73ny9cNDlQ%253D&Expires=1612155456)
- 再畫出id主鍵索引,我們先畫出聚族索引結構圖,如下:
![](https://static01.imgkr.com/temp/771ed84e1cb14b058d6ee5480a713a61.png)
因此,這條 SQL 查詢語句執行大概流程就是醬紫:
- 1. 搜尋`idx_age`索引樹,將磁碟塊1載入到記憶體,由於32<37,搜尋左路分支,到磁碟定址磁碟塊2。
- 2. 將磁碟塊2載入到記憶體中,在記憶體繼續遍歷,找到age=32的記錄,取得id = 400.
- 3. 拿到id=400後,回到id主鍵索引樹。
- 4. 搜尋`id主鍵`索引樹,將磁碟塊1載入記憶體,在記憶體遍歷,找到了400,但是B+樹索引非葉子節點是不儲存資料的。索引會繼續搜尋400的右分支,到磁碟定址磁碟塊3.
- 5. 將磁碟塊3載入記憶體,在記憶體遍歷,找到id=400的記錄,拿到R4這一行的資料,好的,大功告成。
因此,這個SQL查詢,執行了幾次樹的搜尋操作,是不是一步瞭然了呀。**特別的**,在`idx_age`二級索引樹找到主鍵`id`後,回到id主鍵索引搜尋的過程,就稱為回表。
> 什麼是回表?拿到主鍵再回到主鍵索引查詢的過程,就叫做**回表**
### 五、面試官考點之覆蓋索引
**面試官:** 如果不用`select *`, 而是使用`select id,age`,以上的題目執行了幾次樹搜尋操作呢?
**解析:** 這個問題,主要考察候選人的覆蓋索引知識點。回到`idx_age`索引樹,你可以發現查詢選項id和age都在葉子節點上了。因此,可以直接提供查詢結果啦,根本就不需要再回表了~
![](https://imgkr2.cn-bj.ufileos.com/bb15ae6d-3aa2-4459-9eeb-dc063728d0da.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=7eA6vGSG2I%252Bh%252FSlYyAfR%252BNqdrC0%253D&Expires=1612155562)
> 覆蓋索引:在查詢的資料列裡面,不需要回表去查,直接從索引列就能取到想要的結果。換句話說,你SQL用到的索引列資料,覆蓋了查詢結果的列,就算上覆蓋索引了。
所以,相對於上個問題,就是省去了回表的樹搜尋操作。
### 六、面試官考點之索引失效
**面試官:** 如果我現在給`name`欄位加上普通索引,然後用個like模糊搜尋,那會執行多少次查詢呢?SQL如下:
```
select * from employee where name like '%杰倫%';
```
**解析:** 這裡考察的知識點就是,like是否會導致不走索引,看先該SQL的explain執行計劃吧。其實like 模糊搜尋,會導致不走索引的,如下:
![](https://static01.imgkr.com/temp/662ea70ae92442f4b4c7bdaceef0e89c.png)
因此,這條SQL最後就全表掃描啦~日常開發中,這幾種騷操作都可能會導致索引失效,如下:
- 查詢條件包含or,可能導致索引失效
- 如何欄位型別是字串,where時一定用引號括起來,否則索引失效
- like萬用字元可能導致索引失效。
- 聯合索引,查詢時的條件列不是聯合索引中的第一個列,索引失效。
- 在索引列上使用mysql的內建函式,索引失效。
- 對索引列運算(如,+、-、*、/),索引失效。
- 索引欄位上使用(!= 或者 < >,not in)時,可能會導致索引失效。
- 索引欄位上使用is null, is not null,可能導致索引失效。
- 左連線查詢或者右連線查詢查詢關聯的欄位編碼格式不一樣,可能導致索引失效。
- mysql估計使用全表掃描要比使用索引快,則不使用索引。
### 七、面試官考點聯合索引之最左字首原則
**面試官:** 如果我現在給name,age欄位加上聯合索引索引,以下SQL執行多少次樹搜尋呢?先畫下索引樹?
```
select * from employee where name like '小%' order by age desc;
```
**解析:** 這裡考察聯合索引的最左字首原則以及like是否中索引的知識點。組合索引樹示意圖大概如下:
![](https://static01.imgkr.com/temp/40eb224bad4345c7b37b4357c73d775d.png)
聯合索引項是先按姓名name從小到大排序,如果名字name相同,則按年齡age從小到大排序。面試官要求查所有名字第一個字是“小”的人,SQL的like '小%'是可以用上```idx_name_age```聯合索引的。
![](https://imgkr2.cn-bj.ufileos.com/51dc0cf9-acd6-4f61-b575-b24ffd5f83f2.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=xFBsyHWLBQR0H7jDWElHBVXyLRY%253D&Expires=1612155809)
該查詢會沿著idx_name_age索引樹,找到第一個字是小的索引值,因此依次找到```小軍、小倫、小燕、```,分別拿到Id=```600、100、700```,然後回三次表,去找對應的記錄。 這裡面的最左字首```小```,就是字串索引的最左M個字元。實際上,
- 這個最左字首可以是聯合索引的最左N個欄位。比如組合索引(a,b,c)可以相當於建了(a),(a,b),(a,b,c)三個索引,大大提高了索引複用能力。
- 最左字首也可以是字串索引的最左M個字元。
### 八、面試官考點之索引下推
**面試官:** 我們還是居於組合索引 idx_name_age,以下這個SQL執行幾次樹搜尋呢?
```
select * from employee where name like '小%' and age=28 and sex='0';
```
**解析:** 這裡考察索引下推的知識點,如果是**Mysql5.6之前**,在idx_name_age索引樹,找出所有名字第一個字是“小”的人,拿到它們的主鍵id,然後回表找出資料行,再去對比年齡和性別等其他欄位。如圖:
![](https://static01.imgkr.com/temp/c0280ebe8aff483c9dfa47632a91d487.png)
有些朋友可能覺得奇怪,(name,age)不是聯合索引嘛?為什麼選出包含“小”字後,不再順便看下年齡age再回表呢,不是更高效嘛?所以呀,MySQL 5.6 就引入了**索引下推優化**,可以在索引遍歷過程中,對索引中包含的欄位先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數。
因此,MySQL5.6版本之後,選出包含“小”字後,順表過濾age=28,,所以就只需一次回表。
![](https://static01.imgkr.com/temp/26e66fb8e0924aa2ba6a2fe234ed64bf.png)
### 九、 面試官考點之大表新增索引
**面試官:** 如果一張表資料量級是千萬級別以上的,那麼,給這張表新增索引,你需要怎麼做呢?
**解析:** 我們需要知道一點,給表新增索引的時候,是會對錶加鎖的。如果不謹慎操作,有可能出現生產事故的。可以參考以下方法:
- 1.先建立一張跟原表A資料結構相同的新表B。
- 2.在新表B新增需要加上的新索引。
- 3.把原表A資料導到新表B
- 4.rename新表B為原表的表名A,原表A換別的表名;
### 總結與練習
本文主要講解了索引的9大關鍵面試考點,希望對大家有幫助。接下來呢,給大家出一道,有關於我最近業務開發遇到的加索引SQL,看下大家是怎麼回答的,有興趣可以聯絡我,一起討論哈~題目如下:
```
select * from A where type ='1' and status ='s' order by create_time desc;
```
假設type有9種類型,區分度還算可以,status的區分度不高(有3種類型),那麼你是如何加索引呢?
- 是給type加單索引
- 還是(type,status,create_time)聯合索引
- 還是(type,create_time)聯合索引呢?
### 參看與感謝
- [ MySQL有哪些索引型別 ?](https://segmentfault.com/q/1010000003832312 " MySQL有哪些索引型別 ?")
- [大表加索引方案](https://zhuanlan.zhihu.com/p/151460679 "大表加索引方案")
- [MySQL實戰45講](https://time.geekbang.org/column/article/69636 "MySQL實戰