對於一個多列索引,它的存儲順序是先按第一列進行比較,而後是第二列,第三列...這樣。查詢時,若是第一列可以排除的越多,那麼後面列須要判斷的行數就越少,效率越高。html
關於如何判斷哪一個列的過濾效率更高,能夠經過選擇性計算來決定。例如咱們要在books表建立一個name列和author列的索引,能夠計算這兩列各自的選擇性:mysql
select count(distinct name) / count(*) as name, count(distinct author) / count(*) as author from books;
最後得出結果以下:sql
Name數據庫 |
author性能 |
0.95優化 |
0.9spa |
顯然name字段的選擇性更高,那麼若是把name放第一列,在name條件過濾時就能夠排除更多的列,減小接下來 author的過濾。code
其實該建議比上一個建議優先級更高server
例如一個商品管理頁面,通常都是基於該店家的上架或已下架的商品,再添加其餘的查詢條件等等。因爲全部的查詢都須要帶有shopid和status條件,此時應該優先將這兩個條件做爲基本前綴,這樣就能夠方便複用索引。htm
例如一個(shopid, status, createdat)的索引,當查詢條件只有shopid和status時,也可使用該索引。若是徹底根據字段的過濾效率來決定索引,就須要建立不少不一樣的索引。
索引的值都是有序排列的,在建立索引時還能夠顯式指定每一個列的排序方式,例如
create index idx_books_author_created_at on books (author, created_at DESC);
此時,若是執行下面的的查詢
select * from books where author = 'author' order by created_at DESC;
因爲知足auhtor的索引的created_at列都是有序排列的,因此不須要再進行額外的排序操做。
當結果數據集很大時,應該儘量的經過索引來避免查詢的額外排序,由於當內存排序空間(sort_buffer_size)不夠用時,就須要把一部份內容放到硬盤中,此時會很影響性能。
例如一個分頁查詢每頁顯示100條,按從大到小的順序顯示,當瀏覽到第100頁時,若是查詢是file sort的,數據庫須要使用堆排序先計算出這個表裏面前100 * 100 = 10000條最大的數據,而後取9900 - 10000之間的數據返回給客戶端,在計算的過程當中,這個最大堆若是放不下就須要保存到磁盤中,可是又須要頻繁比較和替換。
在以前對硬盤知識瞭解後能夠知道,一次隨機讀會有10ms的尋址延遲,若是一次查詢涉及達到屢次的隨機讀,會很大程度的限制查詢性能。常見的sql查詢形成隨機IO的包括回表和join
例以下面的查詢
select * from books where author = 'author1';
若是author1有100本書,可是這100本書並非連續錄入的,也就是說這100本書在硬盤中的存儲是分離的。那麼在有二級索引(author, created_at)的狀況下,MySQL先經過二級索引找到知足author1的全部books的id,而後再經過id在聚簇索引中找到具體數據。
在這一過程當中,二級索引的存儲能夠認爲是連續的,那麼二級索引耗時就是10ms + 100 * 0.01 = 11ms,包含一次尋址以及接下來的順序讀。而主鍵索引回表形成的隨機IO最差狀況是10ms * 100 = 1000ms。那麼一共就須要11ms + 1000ms = 1011ms
一般減小隨機IO的一種方式就使用覆蓋索引。例如上面的查詢中,若是咱們只是想要該做者的書名,能夠將(author, createdat)擴展爲(author, createdat,name),而後將sql修改以下
select name from books where author = 'author1';
因爲索引中已經有name的信息,此時就不會再次回表,查詢耗時就變成了10ms + 100 * 0.01 = 11ms
值得一提的是mysql5.6新增一個叫作索引條件下推的優化,例如在有索引(author, created_at,name)的狀況下,進行下面的查詢:
select name from books where author = 'author1' and name like '%name%' and created_at > '2020-01-01';
根據最左匹配原則,這個查詢只能用到索引的author字段,若是沒有索引條件下推優化,數據庫須要在二級索引找到知足author條件的全部列id,而後回表找到剩餘信息後,再過濾name和created_at條件。
有了索引條件下推,在找到知足author條件的全部索引後,會再用索引的name字段進行普經過濾,儘可能減小回表的次數,減小隨機IO
以減小隨機IO中的查詢爲例,咱們最終是把(author, createdat)擴展爲(author, createdat,name),而不是建立一個新的(author, name)的索引。
在實際應用場景中也有相似的狀況,例如建立一個userid的外鍵索引,而後又建立(userid, xxx)的索引。因爲索引存儲的順序性,其實能夠將這兩個索引進行合併,若是咱們先建立(userid, xxx)的索引,而後再添加userid的外鍵,mysql會自動使用前面建立索引。
索引是否越多越好呢?
顯然不是,由於索引是對原表的數據冗餘,那麼他就必需要保證數據的一致性。若是原表增長了一條數據,索引也須要增長。若是原表修改了一條數據,那麼對應的索引可能也要修改內容以及排序的位置,這可能會形成頁分裂或頁合併。一個表若是索引過多,那麼維護索引與表的數據一致性也是不小的壓力。一般建議在知足需求前提下,索引越少越好。