提到如何提高MySQL檢索性能,一個很直接的答案就是建立索引,但是索引如果建立不恰當可能會起到相反作用,本文默認引擎為InnoDB來解釋。
聚集索引和非聚集索引
數據庫表一般會將主鍵Id定義為聚集索引,一張表只存在一個聚集索引,并且在聚集索引B+樹的葉子節點上面存放的是整條記錄。
而非聚集索引可以創建很多個(但是一般建議不超過5個),在非聚集索引的B+數上葉子節點上面存放的是主鍵Id。
實例
創建一個MySQL數據庫表,語句如下
CREATE TABLE `Teacher` (
`Id` int NOT NULL AUTO_INCREMENT,
`Name` varchar(20) NOT NULL,
`Age` int NOT NULL,
`Phone` varchar(20) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB
只包含一個聚集索引,創建完成后隨便插入些數據。
回表
假如我們有業務代碼需要很頻繁使用名稱Name去檢索信息,可以創建一個Name的索引,修改表:
ALTER TABLE `Teacher`
ADD INDEX `Idx_name` (`Name`)
接著根據Name查詢:
EXPLAIN SELECT * FROM Teacher WHERE Name='Alex'
注意前面的EXPLAIN關鍵字,這個關鍵字可以顯示語句執行的詳細信息,包含了索引使用情況。
其中possible_keys表示這條語句可用的索引,而后面的key才是真正使用的索引。
實際上引擎會先去訪問Idx_name這個索引獲取到主鍵Id以后再次查詢Primary索引,這就是回表操作。
所以是否可以不回表來進一步提高查詢執行效率?當然可以!
索引覆蓋
索引覆蓋值得是輔助索引中已經包含有想要查詢的字段,因此不用再去主索引中再次進行定位。
假如業務中有很高頻的請求是根據電話去查找名稱,可以建立一個電話-姓名的聯合索引。
ALTER TABLE Teacher
ADD INDEX `Idx_Phone_Name` (`Phone`,`Name`)
執行查詢語句,只查詢姓名:
EXPLAIN SELECT Name FROM Teacher WHERE phone='123'
可以注意到這句查詢使用了Idx_Phone_Name索引并且Extra信息顯示是Using index。
再次執行查詢語句,這次查詢所有的信息:
EXPLAIN SELECT * FROM Teacher WHERE phone='123'
這一次Extra未顯示Using index,說明進行了回表操作。
聯合索引和最左前綴原則
前面其實已經使用了聯合索引,個人覺得聯合索引最大的用處在于減少索引數目,減小索引維護成本,最左前綴原則是指所有和聯合索引從左向右順序相同的查詢都可以使用這個聯合索引。比如創建了一個A_B_C的索引,其實某種程度上就相當于創建了A,A_B,A_B_C三個索引。
索引下推
在MySQL5.6版本之后,數據庫支持了索引下推,看看這個語句:
EXPLAIN SELECT * FROM Teacher WHERE phone='123' AND name LIKE 'alex'
此時Extra中信息為Using index condition
這里查詢的是全部數據,因此不可避免會回表查詢。但是因為存在索引下推,引擎在第一次查找Idx_Phone_Name樹的時候不但會根據Phone的值去做判斷,還會過濾掉不滿足Name條件的記錄,避免無意義的回表操作。
索引丟失
索引丟失指的是存在沒有被使用的索引,這樣維護起來的索引是無效的,造成了性能浪費,開發中需要盡量避免索引丟失的情況。而造成索引丟失的原因大概包括:
- 被索引字段發生了隱式類型轉換
- 被索引字段使用了表達式計算
- 被索引字段使用了函數
- 被索引字段不是聯合索引地最左字段
- like關鍵字前使用了左模糊匹配或者左右模糊匹配
舉個例子
創建一個Phone上面的索引:
ALTER TABLE Teacher
ADD INDEX `Idx_phone` (`Phone`)
然后執行查詢語句,注意這里的給phone的值是數字,MySQL查詢過程中支持類型轉換因此不會報錯:
EXPLAIN SELECT * FROM Teacher where phone=123
可以看到,possible_key中顯示可用索引為Idx_phone,但是實際上使用索引key為空。
其他情況下的索引丟失可以自己去試下。