前面一篇文章,松哥和大家聊了 MySQL 中的索引合并,雖然 MySQL 提供了索引合并機制來提升 SQL 執行的效率,然而在具體實踐中,如果能避免發生索引合并是最好的,畢竟這是沒辦法的辦法,是一個下下策。發生索引合并大概率是因為我們索引在設計的時候就有問題,設計好聯合索引,我們就能在一定程度上避免發生索引合并問題。
1. 聯合索引
1.1 什么是聯合索引
聯合索引就是數據表中的多個字段,共同組成一個索引。由于 InnoDB 中索引的數據結構是一個 B+Tree,當是一個聯合索引的時候,排序的時候會首先按照聯合索引的第一個字段排序,如果第一個字段的值相同,則按照第二個字段排序,如果第二個字段的值也相同,則按照第三個字段排序,以此類推。
舉一個簡單的例子,假設我有如下數據:
id |
username |
age |
address |
gender |
1 |
ab |
99 |
深圳 |
男 |
2 |
ac |
98 |
廣州 |
男 |
3 |
af |
88 |
北京 |
女 |
4 |
bc |
80 |
上海 |
女 |
5 |
bg |
85 |
重慶 |
女 |
6 |
bw |
95 |
天津 |
男 |
7 |
bw |
99 |
海口 |
女 |
8 |
cc |
92 |
武漢 |
男 |
9 |
ck |
90 |
深圳 |
男 |
10 |
cx |
93 |
深圳 |
男 |
現在我給 username 和 age 字段建立聯合索引,那么 B+Tree 在排序的時候,會首先按照 username 排序,當 username 相同的時候,再按照 age 進行排序。畫出來的 B+Tree 如下圖:
如上圖,bw 相同的時候,按照 age 進行排序。
如果我們想要在 MySQL 中,讓聯合索引發揮最大作用,就要充分考慮到聯合索引中各字段的順序。
1.2 聯合索引順序要考慮哪些因素?
在設計聯合索引的時候,我們最容易想到的原則是查詢條件影響了聯合索引中各個字段的順序,要根據查詢條件來設計聯合索引中各個字段的順序。
實際上,除了上面提到的查詢條件之外,聯合索引的順序還會影響到查詢的排序和分組等,所以,設計聯合索引的順序可以算是一個真真正正的技術活。
2. 案例分析
松哥這里還是使用官方的案例吧,小伙伴們在公眾號后臺回復 mysql官方案例 可以獲取到這個數據庫腳本的下載地址。
在 MySQL 的官方案例中,有一個支付表 payment,如下圖:
小伙伴們從圖中可以看到,這個表中有一個 customer_id 和一個 staff_id,現在假設我想要按照這兩個來進行搜索,例如執行如下 SQL:
select * from payment where customer_id=1 and staff_id=2;
- 1.
查詢條件有兩個,我想建立一個聯合索引,那么究竟是把 customer_id 放在前面還是把 staff_id 放在前面呢?
一個比較常用的法則是看字段的選擇性,選擇性高的字段應該是放在前面。有的小伙伴可能還不清楚什么是字段的選擇性,可以參考松哥之前的文章:前綴索引,在性能和空間中尋找平衡。
那么怎么獲取各個字段的選擇性呢?這個很好計算,一個 SQL 搞定,如下:
select count(distinct customer_id)/count(1) as c,count(distinct staff_id)/count(1) as s from payment;
- 1.
執行結果如下:
可以看到,customer_id 的選擇性為 0.0373,而 staff_id 的選擇性為 0.0001,那么在建立聯合索引的時候,將 customer_id 放在第一列顯然更合適一些,因為它的選擇性更高(意味著字段里邊重復的值相對來說會少一些),根據 customer_id 更容易鎖定一行,查詢效率要更高一些。
不過需要注意,上面的法則并非放之四海而皆準,還是要具體問題具體分析。在一些特別極端的情況下,索引選擇性非常之低,那個時候就沒有必要建立聯合索引了。特殊情況甚至需要我們從業務邏輯上去解決。
松哥舉一個例子來說明這個問題。
在我第一版的 vhr 中,當時有一個系統通知的功能,就是管理員可以給所有的用戶群發消息。用戶之間也可以互發消息,如果發送消息的時候,用戶不在線,就需要先把消息存到數據庫中,等用戶上線了再推給用戶,那么就需要一張表來保存消息。這個表中有一個字段就是消息發送者,由于網站經常需要發送通知,就導致這個字段的值分布非常不均,大約有 50% 的值都是 admin,剩下的 50% 則是其他普通用戶,那么查詢的時候,據此字段建立的聯合索引,如果查詢條件不是 admin,則過濾效果不錯,如果查詢條件是 admin,則過濾效果就非常差。對于這樣的問題,我們就需要從業務上去解決,例如禁止根據 admin 去查詢等等。總之,建立聯合索引時,我們前面所所說的字段選擇性最高的原則,并不是放之四海而皆準的,小伙伴們還是要具體情況具體分析。
3. 注意事項
由于聯合索引也是存儲在 B+Tree 中,如 1.1 小節圖示,username 在整棵 B+Tree 中是有序的,但是從整體上來看,age 是無序的,所以對于聯合索引在搜索的時候,需要滿足最做匹配原則才是有效的,否則會失效。舉例來說,如果查詢條件里只有 age,則索引就會失效,因為順著索引的 B+Tree 去查詢滿足條件的記錄,得一個一個找,還不如直接遍歷主鍵索引。