MySQL 索引的面試題總結
什麼是索引?
索引是一種能幫助 MySQL 提高查詢效率的資料結構。
索引分別有哪些優點和缺點?
索引的優點如下:
- 快速訪問資料表中的特定資訊,提高檢索速度。
- 建立唯一性索引,保證資料表中每一行資料的唯一性。
- 加速表與表之間的連線。
- 使用分組和排序進行資料檢索時,可以顯著減少查詢中分組和排序的時間。
索引的缺點:
- 雖然提高了的查詢速度,但卻降低了更新表的速度,比如 update、insert,因為更新資料時,MySQL 不僅要更新資料,還要更新索引檔案;
- 建立索引會佔用磁碟檔案的索引檔案。
使用索引注意事項:
- 使用短索引,短索引不僅可以提高查詢速度,更能節省磁碟空間和 I/O 操作;
- 索引列排序,MySQL 查詢只使用一個索引,因此如果 where 子句中已經使用了索引的話,那麼 order by 中的列是不會使用索引的,因此資料庫預設排序可以符合要求的情況下,不要進行排序操作;儘量不要包含多個列的排序,如果需要最好給這些列建立複合索引;
- like 語句操作,一般情況下不鼓勵使用 like 操作,如果非使用不可, 注意 like "%aaa%" 不會使用索引,而like "aaa%"可以使用索引;
- 不要在列上進行運算;
- 不適用 NOT IN 和 <> 操作。
以下 SQL 有什麼問題?該如何優化?
select * from t where f/2=100;
該 SQL 會導致引擎放棄索引而全表掃描,儘量避免在索引列上計算。可改為:
select * from t where f=100*2;
為什麼 MySQL 官方建議使用自增主鍵作為表的主鍵?
因為自增主鍵是連續的,在插入過程中儘量減少頁分裂,即使要進行頁分裂,也只會分裂很少一部分;並且自增主鍵也能減少資料的移動,每次插入都是插入到最後,所以自增主鍵作為表的主鍵,對於表的操作來說效能是最高的。
自增主鍵有哪些優缺點?
優點:
- 資料儲存空間很小;
- 效能最好;
- 減少頁分裂。
缺點:
- 資料量過大,可能會超出自增長取值範圍;
- 無法滿足分散式儲存,分庫分表的情況下無法合併表;
- 主鍵有自增規律,容易被破解;
綜上所述:是否需要使用自增主鍵,需要根據自己的業務場景來設計。如果是單表單庫,則優先考慮自增主鍵,如果是分散式儲存,分庫分表,則需要考慮資料合併的業務場景來做資料庫設計方案。
索引有幾種型別?分別如何建立?
MySQL 的索引有兩種分類方式:邏輯分類和物理分類。 按照邏輯分類,索引可分為:
- 主鍵索引:一張表只能有一個主鍵索引,不允許重複、不允許為 NULL;
- 唯一索引:資料列不允許重複,允許為 NULL 值,一張表可有多個唯一索引,但是一個唯一索引只能包含一列,比如身份證號碼、卡號等都可以作為唯一索引;
- 普通索引:一張表可以建立多個普通索引,一個普通索引可以包含多個欄位,允許資料重複,允許 NULL 值插入;
- 全文索引:讓搜尋關鍵詞更高效的一種索引。
按照物理分類,索引可分為:
- 聚集索引:一般是表中的主鍵索引,如果表中沒有顯示指定主鍵,則會選擇表中的第一個不允許為 NULL 的唯一索引,如果還是沒有的話,就採用 Innodb 儲存引擎為每行資料內建的 6 位元組 ROWID 作為聚集索引。每張表只有一個聚集索引,因為聚集索引的鍵值的邏輯順序決定了表中相應行的物理順序。聚集索引在精確查詢和範圍查詢方面有良好的效能表現(相比於普通索引和全表掃描),聚集索引就顯得彌足珍貴,聚集索引選擇還是要慎重的(一般不會讓沒有語義的自增 id 充當聚集索引);
- 非聚集索引:該索引中索引的邏輯順序與磁碟上行的物理儲存順序不同(非主鍵的那一列),一個表中可以擁有多個非聚集索引。
各種索引的建立指令碼如下:
-- 建立主鍵索引
alter table t add primary key add (`id`);
-- 建立唯一索引
alter table t add unique (`username`);
-- 建立普通索引
alter table t add index index_name (`username`);
-- 建立全文索引
alter table t add fulltext (`username`);
主索引和唯一索引有什麼區別?
- 主索引不能重複且不能為空,唯一索引不能重複,但可以為空;
- 一張表只能有一個主索引,但可以有多個唯一索引;
- 主索引的查詢效能要高於唯一索引。
在 InnDB 中主鍵索引為什麼比普通索引的查詢效能高?
因為普通索引的查詢會多執行一次檢索操作。比如主鍵查詢 select * from t where id=10 只需要搜尋 id 的這棵 B+ 樹,而普通索引查詢 select * from t where f=3 會先查詢 f 索引樹,得到 id 的值之後再去搜索 id 的 B+ 樹,因為多執行了一次檢索,所以執行效率就比主鍵索引要低。
什麼叫回表查詢?
普通索引查詢到主鍵索引後,回到主鍵索引樹搜尋的過程,我們稱為回表查詢。
參考SQL:
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
如果語句是 select * from T where ID=500,即主鍵查詢方式,則只需要檢索主鍵 ID 欄位。
mysql> select * from T where ID=500;
+-----+---+-------+
| id | k | name |
+-----+---+-------+
| 500 | 5 | name5 |
+-----+---+-------+
如果語句是 select * from T where k=5,即普通索引查詢方式,則需要先搜尋 k 索引樹,得到 ID 的值為 500,再到 ID 索引樹搜尋一次,這個過程稱為回表查詢。
mysql> select * from T where k=5;
+-----+---+-------+
| id | k | name |
+-----+---+-------+
| 500 | 5 | name5 |
+-----+---+-------+
也就是說,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該儘量使用主鍵查詢。
如何查詢一張表的所有索引?
SHOW INDEX FROM T 查詢表 T 所有索引。
MySQL 最多可以建立多少個索引列?
MySQL 中最多可以建立 16 個索引列。
以下 like 查詢會使用索引的是哪一個選項?為什麼?
A.like '%A%' B.like '%A' C.like 'A%' D.以上都不是 答:C 題目解析:like 查詢要走索引,查詢字元不能以萬用字元(%)開始。
如何讓 like %abc 走索引查詢?
我們知道如果要讓 like 查詢要走索引,查詢字元不能以萬用字元(%)開始,如果要讓 like %abc 也走索引,可以使用 REVERSE() 函式來建立一個函式索引,查詢指令碼如下:
select * from t where reverse(f) like reverse('%abc');
MySQL 聯合索引應該注意什麼?
聯合索引又叫複合索引,MySQL 中的聯合索引,遵循最左匹配原則,比如,聯合索引為 key(a,b,c),則能觸發索引的搜尋組合是 a|ab|abc 這三種查詢。
聯合索引的作用是什麼?
聯合索引的作用如下:
- 用於多欄位查詢,比如,建了一個 key(a,b,c) 的聯合索引,那麼實際等於建了 key(a)、key(a,b)、key(a,b,c) 等三個索引,我們知道,每多一個索引,就會多一些寫操作和佔用磁碟空間的開銷,尤其是對大資料量的表來說,這可以減少一部分不必要的開銷;
- 覆蓋索引,比如,對於聯合索引 key(a,b,c) 來說,如果使用 SQL:select a,b,c from table where a=1 and b = 1 ,就可以直接通過遍歷索引取得資料,而無需回表查詢,這就減少了隨機的 IO 操作,減少隨機的 IO 操作,可以有效的提升資料庫查詢的效能,是非常重要的資料庫優化手段之一;
- 索引列越多,通過索引篩選出的資料越少。
什麼是最左匹配原則?它的生效原則有哪些?
最左匹配原則也叫最左字首原則,是 MySQL 中的一個重要原則,說的是索引以最左邊的為起點任何連續的索引都能匹配上,當遇到範圍查詢(>、<、between、like)就會停止匹配。 生效原則來看以下示例,比如表中有一個聯合索引欄位 index(a,b,c):
- where a=1 只使用了索引 a;
- where a=1 and b=2 只使用了索引 a,b;
- where a=1 and b=2 and c=3 使用a,b,c;
- where b=1 or where c=1 不使用索引;
- where a=1 and c=3 只使用了索引 a;
- where a=3 and b like 'xx%' and c=3 只使用了索引 a,b。
列值為 NULL 時,查詢會使用到索引嗎?
在 MySQL 5.6 以上的 InnoDB 儲存引擎會正常觸發索引。但為了相容低版本的 MySQL 和相容其他資料庫儲存引擎,不建議使用 NULL 值來儲存和查詢資料,建議設定列為 NOT NULL,並設定一個預設值,比如 0 和空字串等,如果是 datetime 型別,可以設定成 1970-01-01 00:00:00 這樣的特殊值。
以下語句會走索引麼?
select * from t where year(date)>2018;
不會,因為在索引列上涉及到了運算。
能否給手機號的前 6 位建立索引?如何建立?
可以,建立方式有兩種:
- alter table t add index index_phone(phone(6));
- create index index_phone on t(phone(6));
什麼是字首索引?
字首索引也叫區域性索引,比如給身份證的前 10 位新增索引,類似這種給某列部分資訊新增索引的方式叫做字首索引。
為什麼要用字首索引?
字首索引能有效減小索引檔案的大小,讓每個索引頁可以儲存更多的索引值,從而提高了索引查詢的速度。但字首索引也有它的缺點,不能在 order by 或者 group by 中觸發字首索引,也不能把它們用於覆蓋索引。
什麼情況下適合使用字首索引?
當字串本身可能比較長,而且前幾個字元就開始不相同,適合使用字首索引;相反情況下不適合使用字首索引,比如,整個欄位的長度為 20,索引選擇性為 0.9,而我們對前 10 個字元建立字首索引其選擇性也只有 0.5,那麼我們需要繼續加大字首字元的長度,但是這個時候字首索引的優勢已經不明顯,就沒有建立字首索引的必要了。
什麼是頁?
頁是計算機管理儲存器的邏輯塊,硬體及作業系統往往將主存和磁碟儲存區分割為連續的大小相等的塊,每個儲存塊稱為一頁。主存和磁碟以頁為單位交換資料。資料庫系統的設計者巧妙利用了磁碟預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次磁碟 IO 就可以完全載入。
索引的常見儲存演算法有哪些?
- 雜湊儲存法:以 key、value 方式儲存,把值存入陣列中使用雜湊值確認資料的位置,如果發生雜湊衝突,使用連結串列儲存資料;
- 有序陣列儲存法:按順序儲存,優點是可以使用二分法快速找到資料,缺點是更新效率,適合靜態資料儲存;
- 搜尋樹:以樹的方式進行儲存,查詢效能好,更新速度快。
InnoDB 為什麼要使用 B+ 樹,而不是 B 樹、Hash、紅黑樹或二叉樹?
因為 B 樹、Hash、紅黑樹或二叉樹存在以下問題:
- B 樹:不管葉子節點還是非葉子節點,都會儲存資料,這樣導致在非葉子節點中能儲存的指標數量變少(有些資料也稱為扇出),指標少的情況下要儲存大量資料,只能增加樹的高度,導致IO操作變多,查詢效能變低;
- Hash:雖然可以快速定位,但是沒有順序,IO 複雜度高;
- 二叉樹:樹的高度不均勻,不能自平衡,查詢效率跟資料有關(樹的高度),並且 IO 代價高;
- 紅黑樹:樹的高度隨著資料量增加而增加,IO 代價高。
為什麼 InnoDB 要使用 B+ 樹來儲存索引?
B+Tree 中的 B 是 Balance,是平衡的意思,它在經典 B Tree 的基礎上進行了優化,增加了順序訪問指標,在B+Tree 的每個葉子節點增加一個指向相鄰葉子節點的指標,就形成了帶有順序訪問指標的 B+Tree,這樣就提高了區間訪問效能:如果要查詢 key 為從 18 到 49 的所有資料記錄,當找到 18 後,只需順著節點和指標順序遍歷就可以一次性訪問到所有資料節點,極大提到了區間查詢效率(無需返回上層父節點重複遍歷查詢減少 IO 操作)。
索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存的磁碟上,這樣的話,索引查詢過程中就要產生磁碟 IO 消耗,相對於記憶體存取,IO 存取的消耗要高几個數量級,所以索引的結構組織要儘量減少查詢過程中磁碟 IO 的存取次數,從而提升索引效率。 綜合所述,InnDB 只有採取 B+ 樹的資料結構儲存索引,才能提供資料庫整體的操作效能。
唯一索引和普通索引哪個效能更好?
- 對於查詢操作來說:普通索引和唯一索引的效能相近,都是從索引樹中進行查詢;
- 對於更新操作來說:唯一索引要比普通索引執行的慢,因為唯一索引需要先將資料讀取到記憶體中,再在記憶體中進行資料的唯一效驗,所以執行起來要比普通索引更慢。
優化器選擇查詢索引的影響因素有哪些?
優化器的目的是使用最小的代價選擇最優的執行方案,影響優化器選擇索引的因素如下:
- 掃描行數,掃描的行數越少,執行代價就越少,執行效率就會越高;
- 是否使用了臨時表;
- 是否排序。
MySQL 是如何判斷索引掃描行數的多少?
MySQL 的掃描行數是通過索引統計列(cardinality)大致得到並且判斷的,而索引統計列(cardinality)可以通過查詢命令 show index 得到,索引掃描行數的多少就是通過這個值進行判斷的。
MySQL 是如何得到索引基數的?它準確嗎?
MySQL 的索引基數並不準確,因為 MySQL 的索引基數是通過取樣統計得到的,比如 InnoDb 預設會有 N 個數據頁,取樣統計會統計這些頁面上的不同值得到一個平均值,然後除以這個索引的頁面數就得到了這個索引基數。
MySQL 如何指定查詢的索引?
在 MySQL 中可以使用 force index 強行選擇一個索引,具體查詢語句如下:
select * from t force index(index_t)
在 MySQL 中指定了查詢索引,為什麼沒有生效?
我們知道在 MySQL 中使用 force index 可以指定查詢的索引,但並不是一定會生效,原因是 MySQL 會根據優化器自己選擇索引,如果 force index 指定的索引出現在候選索引上,這個時候 MySQL 不會在判斷掃描的行數的多少直接使用指定的索引,如果沒在候選索引中,即使 force index 指定了索引也是不會生效的。
以下 or 查詢有什麼問題嗎?該如何優化?
select * from t where num=10 or num=20;
答:如果使用 or 查詢會使 MySQL 放棄索引而全表掃描,可以改為:
select * from t where num=10 union select * from t where num=20;
以下查詢要如何優化?
表中包含索引:
- KEY mid (mid)
- KEY begintime (begintime)
- KEY dg (day,group)
使用以下 SQL 進行查詢:
select f from t where day='2010-12-31' and group=18 and begintime<'2019-12-31 12:14:28' order by begintime limit 1;
答:此查詢理論上是使用 dg 索引效率更高,通過 explain 可以對比查詢掃描次數。由於使用了 order by begintime 則使查詢放棄了 dg 索引,而使用 begintime 索引,從側面印證 order by 關鍵字會影響查詢使用索引,這時可以使查詢強制使用索引,改為以下SQL:
select f from t use index(dg) where day='2010-12-31' and group=18 and begintime< '2019-12-31 12:14:28' order by begintime limit 1;
MySQL 會錯選索引嗎?
MySQL 會錯選索引,比如 k 索引的速度更快,但是 MySQL 並沒有使用而是採用了 v 索引,這種就叫錯選索引,因為索引選擇是 MySQL 的服務層的優化器來自動選擇的,但它在複雜情況下也和人寫程式一樣出現缺陷。
如何解決 MySQL 錯選索引的問題?
- 刪除錯選的索引,只留下對的索引;
- 使用 force index 指定索引;
- 修改 SQL 查詢語句引導 MySQL 使用我們期望的索引,比如把 order by b limit 1 改為 order by b,a limit 1 語義是相同的,但 MySQL 查詢的時候會考慮使用 a 鍵上的索引。
如何優化身份證的索引?
在中國因為前 6 位代表的是地區,所以很多人的前六位都是相同的,如果我們使用字首索引為 6 位的話,效能提升也並不是很明顯,但如果設定的位數過長,那麼佔用的磁碟空間也越大,資料頁能放下的索引值就越少,搜尋效率也越低。針對這種情況優化方案有以下兩種:
- 使用身份證倒序儲存,這樣設定前六位的意義就很大了;
- 使用 hash 值,新建立一個欄位用於儲存身份證的 hash 值。