MySQL常見面試題索引、表設計


 正確使用索引的條件      
 
        1.建立索引的列的重復度不能太高
        2.條件列不能參與計算
        3.不能使用函數
        4.條件中不能使用范圍
        5.不要使用like '%c'
        6.條件中用or
            a=0 or b=1 or c=2 or d=4 只要其中一列沒有索引就無法命中
        7.最左前綴(a,b,c,d) 只適用於and條件的列,如果出現范圍,從出現范圍的字段開始就失效,必須帶着最左側的列
            只要不帶a就無法命中索引
            a = 1 or b = 2   無法命中索引
            a = 1 and b>100 a可以命中索引 b,c,d無法命中
            a>200 無法命中索引  因為聯合索引一旦使用范圍,從使用范圍開始之后的索引都不生效    

  

1

索引概念、索引模型

 

Q:你們每天這么大的數據量,都是保存在關系型數據庫中嗎?

A:是的,我們線上使用的是MySQL數據庫 

Q:每天幾百萬數據,一個月就是幾千萬了,那你們有沒有對於查詢做一些優化呢?

A:我們在數據庫中創建了一些索引(我現在非常后悔我當時說了這句話)

 

Q:那你能說說什么是索引嗎?

A:索引其實是一種數據結構,能夠幫助我們快速的檢索數據庫中的數據

 

Q:那么索引具體采用的哪種數據結構呢? 

A:常見的MySQL主要有兩種結構:Hash索引和B+ Tree索引,我們使用的是InnoDB引擎,默認的是B+樹

 

Q:既然你提到InnoDB使用的B+ 樹的索引模型,那么你知道為什么采用B+ 樹嗎?這和Hash索引比較起來有什么優缺點嗎?

A:因為Hash索引底層是哈希表,哈希表是一種以key-value存儲數據的結構,所以多個數據在存儲關系上是完全沒有任何順序關系的,所以,對於區間查詢是無法直接通過索引查詢的,就需要全表掃描。所以,哈希索引只適用於等值查詢的場景。而B+ 樹是一種多路平衡查詢樹,所以他的節點是天然有序的(左子節點小於父節點、父節點小於右子節點),所以對於范圍查詢的時候不需要做全表掃描

 

Q:除了上面這個范圍查詢的,你還能說出其他的一些區別嗎? 

A: 

B+Tree索引和Hash索引區別?

哈希索引適合等值查詢,但是無法進行范圍查詢 

哈希索引沒辦法利用索引完成排序 

哈希索引不支持多列聯合索引的最左匹配規則 

如果有大量重復鍵值的情況下,哈希索引的效率會很低,因為存在哈希碰撞問題

 

2

聚簇索引、覆蓋索引

 

Q:剛剛我們聊到B+ Tree ,那你知道B+ Tree的葉子節點都可以存哪些東西嗎?

A:InnoDB的B+ Tree可能存儲的是整行數據,也有可能是主鍵的值

 

Q:那這兩者有什么區別嗎? 

A:在 InnoDB 里,索引B+ Tree的葉子節點存儲了整行數據的是主鍵索引,也被稱之為聚簇索引。而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱之為非聚簇索引

 

Q:那么,聚簇索引和非聚簇索引,在查詢數據的時候有區別嗎?

A:聚簇索引查詢會更快?

 

Q:為什么呢? 

A:因為主鍵索引樹的葉子節點直接就是我們要查詢的整行數據了。而非主鍵索引的葉子節點是主鍵的值,查到主鍵的值以后,還需要再通過主鍵的值再進行一次查詢

 

Q:剛剛你提到主鍵索引查詢只會查一次,而非主鍵索引需要回表查詢多次。是所有情況都是這樣的嗎?非主鍵索引一定會查詢多次嗎?

A:通過覆蓋索引也可以只查詢一次。覆蓋索引(covering index)指一個查詢語句的執行只用從索引中就能夠取得,不必從數據表中讀取。也可以稱之為實現了索引覆蓋。

當一條查詢語句符合覆蓋索引條件時,MySQL只需要通過索引就可以返回查詢所需要的數據,這樣避免了查到索引后再返回表操作,減少I/O提高效率。如,表covering_index_sample中有一個普通索引 idx_key1_key2(key1,key2)。當我們通過SQL語句:select key2 from covering_index_sample where key1 = 'keytest';的時候,就可以通過覆蓋索引查詢,無需回表。

 

3

聯合索引、最左前綴匹配

 

Q:不知道的話沒關系,想問一下,你們在創建索引的時候都會考慮哪些因素呢?

A:我們一般對於查詢概率比較高,經常作為where條件的字段設置索引

 

Q: 那你們有用過聯合索引嗎? 

A:用過呀,我們有對一些表中創建過聯合索引

 

Q:那你們在創建聯合索引的時候,需要做聯合索引多個字段之間順序你們是如何選擇的呢? 

A:我們把識別度最高的字段放到最前面

 

Q:為什么這么做呢?

A:這樣的話可能命中率會高一點吧。。。

 

Q: 那你知道最左前綴匹配嗎?

A:在創建多列索引時,我們根據業務需求,where子句中使用最頻繁的一列放在最左邊,因為MySQL索引查詢會遵循最左前綴匹配的原則,即最左優先,在檢索數據時從聯合索引的最左邊開始匹配。所以當我們創建一個聯合索引的時候,如(key1,key2,key3),相當於創建了(key1)、(key1,key2)和(key1,key2,key3)三個索引,這就是最左匹配原則

 

 

 

4

索引下推、查詢優化

 

Q:你們線上用的MySQL是哪個版本啊呢? 

A:我們MySQL是5.7 

 

Q:那你知道在MySQL 5.6中,對索引做了哪些優化嗎? 

A:Index Condition Pushdown(索引下推)

MySQL 5.6引入了索引下推優化,默認開啟,使用SET optimizer_switch = 'index_condition_pushdown=off';可以將其關閉。官方文檔中給的例子和解釋如下:

people表中(zipcode,lastname,firstname)構成一個索引

SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';

如果沒有使用索引下推技術,則MySQL會通過zipcode='95054'從存儲引擎中查詢對應的數據,返回到MySQL服務端,然后MySQL服務端基於lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷數據是否符合條件。

如果使用了索引下推技術,則MYSQL首先會返回符合zipcode='95054'的索引,然后根據lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷索引是否符合條件。如果符合條件,則根據該索引來定位對應的數據,如果不符合,則直接reject掉。有了索引下推優化,可以在有like條件查詢的情況下,減少回表次數。

 

Q:你們創建的那么多索引,到底有沒有生效,或者說你們的SQL語句有沒有使用索引查詢你們有統計過嗎?

A:這個還沒有統計過,除非遇到慢SQL的時候我們才會去排查 

 

Q:那排查的時候,有什么手段可以知道有沒有走索引查詢呢?

A:可以通過explain查看sql語句的執行計划,通過執行計划來分析索引使用情況

 

Q:那什么情況下會發生明明創建了索引,但是執行的時候並沒有通過索引呢? 

A:查詢優化器

一條SQL語句的查詢,可以有不同的執行方案,至於最終選擇哪種方案,需要通過優化器進行選擇,選擇執行成本最低的方案。

在一條單表查詢語句真正執行之前,MySQL的查詢優化器會找出執行該語句所有可能使用的方案,對比之后找出成本最低的方案。

這個成本最低的方案就是所謂的執行計划。優化過程大致如下:

1、根據搜索條件,找出所有可能使用的索引 

2、計算全表掃描的代價 

3、計算使用不同索引執行查詢的代價 

4、對比各種執行方案的代價,找出成本最低的那一個

 

 

 

 

問題1:為什么一定要設一個主鍵?
回答:因為你不設主鍵的情況下,innodb也會幫你生成一個隱藏列,作為自增主鍵。所以啦,反正都要生成一個主鍵,那你還不如自己指定一個主鍵,在有些情況下,就能顯式的用上主鍵索引,提高查詢效率!

問題2:主鍵是用自增還是UUID?
回答:肯定答自增啊。innodb 中的主鍵是聚簇索引。如果主鍵是自增的,那么每次插入新的記錄,記錄就會順序添加到當前索引節點的后續位置,當一頁寫滿,就會自動開辟一個新的頁。如果不是自增主鍵,那么可能會在中間插入,就會引發頁的分裂,產生很多表碎片!。
上面那句話看不懂沒事,大白話一句就是:用自增插入性能好!


另外,附一個測試表給你們,表名帶uuid的就是用uuid作為主鍵。大家看一下就知道性能差距了:

 


如上圖所示,當主鍵是UUID的時候,插入時間更長,而且占用空間更大!

額,大家千萬不要忘了,當你回答自增主鍵后,想一下《自增主鍵用完該怎么辦?

ps:這個問題,你要是能把UUID講出合理的理由也行。

問題3:主鍵為什么不推薦有業務含義?
回答:有如下兩個原因

  • (1)因為任何有業務含義的列都有改變的可能性,主鍵一旦帶上了業務含義,那么主鍵就有可能發生變更。主鍵一旦發生變更,該數據在磁盤上的存儲位置就會發生變更,有可能會引發頁分裂,產生空間碎片。

  • (2)帶有業務含義的主鍵,不一定是順序自增的。那么就會導致數據的插入順序,並不能保證后面插入數據的主鍵一定比前面的數據大。如果出現了,后面插入數據的主鍵比前面的小,就有可能引發頁分裂,產生空間碎片。

問題4:表示枚舉的字段為什么不用enum類型?
回答:在工作中表示枚舉的字段,一般用tinyint類型。
那為什么不用enum類型呢?下面兩個原因
(1)ENUM類型的ORDER BY操作效率低,需要額外操作
(2)如果枚舉值是數值,有陷阱
舉個例子,表結構如下

CREATE TABLE test (foobar ENUM('0''1''2'));

此時,你執行語句

mysql> INSERT INTO test VALUES (1);

查詢出的結果為

foobar
0

就產生了一個坑爹的結果。
插入語句應該像下面這么寫,插入的才是1

mysql> INSERT INTO test VALUES (`1`);

問題5:貨幣字段用什么類型?
回答:如果貨幣單位是分,可以用Int類型。如果堅持用元,用Decimal
千萬不要答floatdouble,因為float和double是以二進制存儲的,所以有一定的誤差。
打個比方,你建一個列如下

CREATE TABLE `t` (
  `price` float(10,2DEFAULT NULL,
ENGINE=InnoDB DEFAULT CHARSET=utf8

然后insert給price列一個數據為1234567.23,你會發現顯示出來的數據變為1234567.25,精度失准!

問題6:時間字段用什么類型?
回答:此題無固定答案,應結合自己項目背景來答!把理由講清楚就行!
(1)varchar,如果用varchar類型來存時間,優點在於顯示直觀。但是坑的地方也是挺多的。比如,插入的數據沒有校驗,你可能某天就發現一條數據為2013111的數據,請問這是代表2013年1月11日,還是2013年11月1日?
其次,做時間比較運算,你需要用STR_TO_DATE等函數將其轉化為時間類型,你會發現這么寫是無法命中索引的。數據量一大,是個坑!

(2)timestamp,該類型是四個字節的整數,它能表示的時間范圍為1970-01-01 08:00:01到2038-01-19 11:14:07。2038年以后的時間,是無法用timestamp類型存儲的。
但是它有一個優勢,timestamp類型是帶有時區信息的。一旦你系統中的時區發生改變,例如你修改了時區

SET TIME_ZONE = "america/new_york";

你會發現,項目中的該字段的值自己會發生變更。這個特性用來做一些國際化大項目,跨時區的應用時,特別注意!

(3)datetime,datetime儲存占用8個字節,它存儲的時間范圍為1000-01-01 00:00:00 ~ 9999-12-31 23:59:59。顯然,存儲時間范圍更大。但是它坑的地方在於,他存儲的是時間絕對值,不帶有時區信息。如果你改變數據庫的時區,該項的值不會自己發生變更!

(4)bigint,也是8個字節,自己維護一個時間戳,表示范圍比timestamp大多了,就是要自己維護,不大方便。

問題7:為什么不直接存儲圖片、音頻、視頻等大容量內容?
回答:我們在實際應用中,都是用HDFS來存儲文件。然后mysql中,只存文件的存放路徑。mysql中有兩個字段類型被用來設計存放大容量文件,也就是textblob類型。但是,我們在生產中,基本不用這兩個類型!
主要原因有如下兩點

  • (1)Mysql內存臨時表不支持TEXT、BLOB這樣的大數據類型,如果查詢中包含這樣的數據,在排序等操作時,就不能使用內存臨時表,必須使用磁盤臨時表進行。導致查詢效率緩慢

  • (2)binlog內容太多。因為你數據內容比較大,就會造成binlog內容比較多。大家也知道,主從同步是靠binlog進行同步,binlog太大了,就會導致主從同步效率問題!

因此,不推薦使用textblob類型!

問題8:字段為什么要定義為NOT NULL?
回答:OK,這問題從兩個角度來答
(1)索引性能不好

Mysql難以優化引用可空列查詢,它會使索引、索引統計和值更加復雜。可空列需要更多的存儲空間,還需要mysql內部進行特殊處理。可空列被索引后,每條記錄都需要一個額外的字節,還能導致MYisam 中固定大小的索引變成可變大小的索引。                                                                                 —— 出自《高性能mysql第二版》

(2)查詢會出現一些不可預料的結果
這里舉一個例子,大家就懂了。假設,表結構如下

create table table_2 (
     `id` INT (11NOT NULL,
    name varchar(20NOT NULL
)

表數據是這樣的

id name
1 孤獨煙
3 null
5 肥朝
7 null

你執行語句

select count(namefrom table_2;

你會發現結果為2,但是實際上是有四條數據的!類似的查詢問題,其實有很多,不一一列舉。
記住,因為null列的存在,會出現很多出人意料的結果,從而浪費開發時間去排查Bug.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM