理解索引:索引優化

情情說發表於2018-06-04

最近有個需求,要修改現有儲存結構,涉及查詢條件和查詢效率的考量,看了幾篇索引和HBase相關的文章,回憶了相關知識,結合專案需求,說說自己的理解和總結。

錯過的朋友可以先回顧下前3篇文章:

  1. 索引結構和資料定位過程
  2. 查詢過程和高階查詢
  3. 執行計劃詳細介紹

上一篇詳細介紹了explain命令,通過該命令,可以定位出在哪一步出現了效能問題,下一步就是通過優化索引來解決它。

部分內容摘錄了幾個博友的文章,最後會給出文章連結,感謝他們的精彩分析。

常見優化方法

聯合索引最左字首原則

複合索引遵守「最左字首」原則,查詢條件中,使用了複合索引前面的欄位,索引才會被使用,如果不是按照索引的最左列開始查詢,則無法使用索引。

比如在(a,b,c)三個欄位上建立聯合索引,那麼它能夠加快a|(a,b)|(a,b,c)三組查詢的速度,而不能加快b|(b,a)這種查詢順序。

另外,建聯合索引的時候,區分度最高的欄位在最左邊。

不要在列上使用函式和進行運算

不要在列上使用函式,這將導致索引失效而進行全表掃描。

例如下面的 SQL 語句:

select * from artile where YEAR(create_time) <= '2018'; 
複製程式碼

即使 date 上建立了索引,也會全表掃描,可以把計算放到業務層,這樣做不僅可以節省資料庫的 CPU,還可以起到查詢快取優化效果。

負向條件查詢不能使用索引

負向條件有:!=、<>、not in、not exists、not like 等。

select * from artile where status != 1 and status != 2;
複製程式碼

可以使用in進行優化:

select * from artile where status in (0,3)
複製程式碼
使用覆蓋索引

所謂覆蓋索引,是指被查詢的列,資料能從索引中取得,而不用通過行定位符再到資料表上獲取,能夠極大的提高效能。

可以定義一個讓索引包含的額外的列,即使這個列對於索引而言是無用的。

避免強制型別轉換

當查詢條件左右兩側型別不匹配的時候會發生強制轉換,強制轉換可能導致索引失效而進行全表掃描。

如果phone欄位是varchar型別,則下面的SQL不能命中索引:

select * from user where phone=12345678901;
複製程式碼

可以優化為:

select * from user where phone='12345678901';
複製程式碼
範圍列可以用到索引

範圍條件有:<、<=、>、>=、between等。

範圍列可以用到索引,但是範圍列後面的列無法用到索引,索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引。

更新頻繁、資料區分度不高的欄位上不宜建立索引

更新會變更B+樹,更新頻繁的欄位建立索引會大大降低資料庫效能。

「性別」這種區分度不大的屬性,建立索引沒有意義,不能有效過濾資料,效能與全表掃描類似。

區分度可以使用 count(distinct(列名))/count(*) 來計算,在80%以上的時候就可以建立索引。

索引列不允許為null

單列索引不存null值,複合索引不存全為null的值,如果列允許為 null,可能會得到不符合預期的結果集。

避免使用or來連線條件

應該儘量避免在 where 子句中使用 or 來連線條件,因為這會導致索引失效而進行全表掃描,雖然新版的MySQL能夠命中索引,但查詢優化耗費的 CPU比in多。

模糊查詢

前導模糊查詢不能使用索引,非前導查詢可以。

優化案例

利用延遲關聯或者子查詢優化超多分頁場景

MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行。

當 offset 特別大的時候,效率非常低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行SQL改寫。

可以先快速定位需要獲取的id段,然後再關聯:

selecta.* from 表1 a,(select id from 表1 where 條件 limit 1000000 ,10 ) b where a.id=b.id  
複製程式碼
如果明確知道只有一條結果返回,limit 1 能夠提高效率

雖然自己知道只有一條結果,但資料庫並不知道,明確告訴它,讓它主動停止遊標移動。

如何建立索引
where a=1 and b=1  
where b=1  
where b=1 order by time desc
複製程式碼

建議建立兩個索引,即 idx_ab(a,b) 和 idx_b_time(b,time)

MySQL 的查詢優化器會自動調整where子句的條件順序以使用適合的索引,對於上面的第一條 SQL,如果建立索引為idx_ba(b,a) 也是可以用到索引的。

多值匹配和範圍匹配

假如有聯合索引(empno、title、fromdate),下面的 SQL 是否可以用到索引,如果可以的話,會使用幾個?

select * from employee.titles  
where emp_no between '10001' and'10010'  
and title='軟體工程師'   
and from_date between '2008-01-01'and '2018-01-01'  
複製程式碼

可以使用索引,可以用到索引全部三個列,這個 SQL看起來是用了兩個範圍查詢,但作用於empno上的between實際上相當於in,也就是說empno 實際是多值精確匹配。

在 MySQL 中要謹慎地區分多值匹配和範圍匹配,否則會對 MySQL 的行為產生困惑。

聯合索引的最左匹配原則

假如建立聯合索引(a,b,c),下列語句是否可以使用索引,如果可以,使用了那幾列?

where a= 3  // 是,使用了a列
where a= 3 and b = 5  // 是,使用了ab列  
where a = 3 and c = 4 and b = 5  // 是,使用了 a,b,c 列  
where b= 3 // 否
where a= 3 and c = 4  // 是,使用了a列  
where a = 3 and b > 10 and c = 7  // 是,使用了 a,b 列  
where a = 3 and b like 'xx%' andc = 7  // 是,使用了 a,b 列
複製程式碼
根據區分度建立索引

有如下查詢語句,查詢指定產品已稽核(status=1)的評論:

SELECT user_id,title,content FROM `comment`
WHERE status=1 AND product_id=1
LIMIT 0,5 ;
複製程式碼

可以建立聯合索引,status和product_id,但是哪個放左邊就要計算區分度:

SELECT COUNT(DISTINCT status)/COUNT(*) AS audit_rate,
COUNT(DISTINCT product_id)/COUNT(*) AS product_rate
FROM comment;
複製程式碼

一般product的區分度會高點,可以建立如下索引:

CREATE INDEX idx_productID_Status ON comment(product_id,status)
複製程式碼
排序欄位索引

檢視某個使用者最近20條登入記錄,按時間排序:

select *  from login_history where uid = $uid order by create_time desc limit 20;
複製程式碼

建立uid+timeline複合索引,將排序引入到索引結構中,資料庫負載驟降。

參考文章:

  1. MySQL 索引及優化實戰
  2. 索引使用的注意事項

歡迎掃描下方二維碼,關注我的個人微信公眾號,檢視更多文章 ~

情情說

相關文章