【真·乾貨】MySQL 索引及優化實戰

weixin_34006468發表於2018-10-31

索引概念和作用

索引是一種使記錄有序化的技術,它可以指定按某列/某幾列預先排序,從而大大提高查詢速度(類似於漢語詞典中按照拼音或者筆畫查詢)。

索引的主要作用是加快資料查詢速度,提高資料庫的效能。

MySQL 索引型別

從物理儲存角度上,索引可以分為聚集索引和非聚集索引。

1. 聚集索引(Clustered Index)

聚集索引決定資料在磁碟上的物理排序,一個表只能有一個聚集索引。

2. 非聚集索引(Non-clustered Index)

非聚集索引並不決定資料在磁碟上的物理排序,索引上只包含被建立索引的資料,以及一個行定位符 row-locator,這個行定位符,可以理解為一個聚集索引物理排序的指標,通過這個指標,可以找到行資料。

從邏輯角度,索引可以分為以下幾種。

普通索引:最基本的索引,它沒有任何限制。

唯一索引:與普通索引類似,不同的就是索引列的值必須唯一,但允許有空值。如果是組合索引,則列值的組合必須唯一。

主鍵索引:它是一種特殊的唯一索引,用於唯一標識資料表中的某一條記錄,不允許有空值,一般用     primary key 來約束。主鍵和聚集索引的關係詳見“問題詳解”中的第4題。

聯合索引(又叫複合索引):多個欄位上建立的索引,能夠加速複合查詢條件的檢索。

全文索引:老版本     MySQL 自帶的全文索引只能用於資料庫引擎為 MyISAM 的資料表,新版本 MySQL 5.6 的 InnoDB 支援全文索引。預設 MySQL 不支援中文全文檢索,可以通過擴充套件 MySQL,新增中文全文檢索或為中文內容表提供一個對應的英文索引表的方式來支援中文。

MySQL索引優化規則

可以通過以下規則對 MySQL 索引進行優化。

1.前導模糊查詢不能使用索引。

例如下面 SQL 語句不能使用索引。

select * fromdoc where title like '%XX'

而非前導模糊查詢則可以使用索引,如下面的 SQL 語句。

select * fromdoc where title like 'XX%'

頁面搜尋嚴禁左模糊或者全模糊,如果需要可以用搜尋引擎來解決。

2.union、in、or 都能夠命中索引,建議使用 in。

union:能夠命中索引。

示例程式碼如下:

select * fromdoc where status=1

unionall

select * fromdoc where status=2

直接告訴 MySQL 怎麼做,MySQL 耗費的 CPU 最少,但是一般不這麼寫 SQL。

in:能夠命中索引。

示例程式碼如下:

select * fromdoc where status in (1, 2)

查詢優化耗費的 CPU 比 union all 多,但可以忽略不計,一般情況下建議使用 in

or:新版的 MySQL 能夠命中索引。

示例程式碼如下:

select * fromdoc where status = 1 or status = 2

查詢優化耗費的 CPU 比 in 多,不建議頻繁用 or。

3.負向條件查詢不能使用索引,可以優化為 in 查詢。

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

例如下面程式碼:

select * fromdoc where status != 1 and status != 2

可以優化為 in 查詢:

select * fromdoc where status in (0,3,4)

4.聯合索引最左字首原則(又叫最左側查詢)

如果在(a,b,c)三個欄位上建立聯合索引,那麼它能夠加快 a | (a,b) | (a,b,c) 三組查詢速度。

例如登入業務需求,程式碼如下。

selectuid, login_time from user where login_name=? andpasswd=?

可以建立(login_name, passwd)的聯合索引。

因為業務上幾乎沒有 passwd 的單條件查詢需求,而有很多 login_name 的單條件查詢需求,所以可以建立(login_name, passwd)的聯合索引,而不是(passwd, login_name)。

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

如果建立了(a,b)聯合索引,就不必再單獨建立 a 索引。同理,如果建立了(a,b,c)聯合索引,就不必再單獨建立 a、(a,b) 索引。

存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如     where a>? and b=?,那麼即使 a 的區分度更高,也必須把 b 放在索引的最前列。

最左側查詢需求,並不是指 SQL 語句的 where 順序要和聯合索引一致。

下面的 SQL 語句也可以命中 (login_name, passwd) 這個聯合索引。

selectuid, login_time from user where passwd=? andlogin_name=?

但還是建議 where 後的順序和聯合索引一致,養成好習慣。

5.範圍列可以用到索引(聯合索引必須是最左字首)。

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

範圍列可以用到索引(聯合索引必須是最左字首),但是範圍列後面的列無法用到索引,索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引。

假如有聯合索引 (empno、title、fromdate),那麼下面的 SQL 中 emp_no 可以用到索引,而 title 和 from_date 則使用不到索引。

select * fromemployees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31'

6.把計算放到業務層而不是資料庫層。

在欄位上進行計算不能命中索引。

例如下面的 SQL 語句。

select * fromdoc where YEAR(create_time) <= '2016'

即使 date 上建立了索引,也會全表掃描,可優化為值計算,如下:

select * fromdoc where create_time <= '2016-01-01'

把計算放到業務層。

這樣做不僅可以節省資料庫的 CPU,還可以起到查詢快取優化效果。

比如下面的 SQL 語句:

select * fromorder where date < = CURDATE()

可以優化為:

select * fromorder where date < = '2018-01-2412:00:00'

優化後的 SQL 釋放了資料庫的 CPU 多次呼叫,傳入的 SQL 相同,才可以利用查詢快取。

7.強制型別轉換會全表掃描

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

select * fromuser where phone=13800001234

可以優化為:

select * fromuser where phone='13800001234'

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

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

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

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

9.利用覆蓋索引來進行查詢操作,避免回表。

被查詢的列,資料能從索引中取得,而不用通過行定位符 row-locator 再到 row 上獲取,即“被查詢列要被所建的索引覆蓋”,這能夠加速查詢速度。

例如登入業務需求,程式碼如下。

selectuid, login_time from user where login_name=? andpasswd=?

可以建立(login_name, passwd, login_time)的聯合索引,由於 login_time 已經建立在索引中了,被查詢的 uid 和 login_time 就不用去 row 上獲取資料了,從而加速查詢。

10.如果有 order by、group by 的場景,請注意利用索引的有序性。

order     by 最後的欄位是組合索引的一部分,並且放在索引組合順序的最後,避免出現     file_sort 的情況,影響查詢效能。

例如對於語句 where a=? and b=? order by     c,可以建立聯合索引(a,b,c)。

如果索引中有範圍查詢,那麼索引有序性無法利用,如 WHERE     a>10 ORDER BY b;,索引(a,b)無法排序。

11.使用短索引(又叫字首索引)來優化索引。

字首索引,就是用列的字首代替整個列作為索引 key,當字首長度合適時,可以做到既使得字首索引的區分度接近全列索引,同時因為索引 key 變短而減少了索引檔案的大小和維護開銷,可以使用 count(distinct left(列名, 索引長度))/count(*) 來計算字首索引的區分度。

字首索引兼顧索引大小和查詢速度,但是其缺點是不能用於 ORDER BY 和 GROUP BY 操作,也不能用於覆蓋索引(Covering Index,即當索引本身包含查詢所需全部資料時,不再訪問資料檔案本身),很多時候沒必要對全欄位建立索引,根據實際文字區分度決定索引長度即可。

例如對於下面的 SQL 語句:

SELEC *FROM employees.employees WHERE first_name='Eric'AND last_name='Anido';

我們可以建立索引:(firstname, lastname(4))。

12.建立索引的列,不允許為 null。

單列索引不存 null 值,複合索引不存全為 null 的值,如果列允許為 null,可能會得到“不符合預期”的結果集,所以,請使用 not null 約束以及預設值。

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

MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。

示例如下,先快速定位需要獲取的 id 段,然後再關聯:

selecta.* from 表1 a,(select id from 表1 where 條件 limit100000,20 ) b where a.id=b.id

14.業務上具有唯一特性的欄位,即使是多個欄位的組合,也必須建成唯一索引。

不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查詢速度是明顯的。另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒資料產生。

15.超過三個表最好不要 join。

需要 join 的欄位,資料型別必須一致,多表關聯查詢時,保證被關聯的欄位需要有索引。

16.如果明確知道只有一條結果返回,limit 1 能夠提高效率。

比如如下 SQL 語句:

select * fromuser where login_name=?

可以優化為:

select * fromuser where login_name=? limit 1

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

17.SQL 效能優化 explain 中的 type:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts 最好。

consts:單表中最多隻有一個匹配行(主鍵或者唯一索引),在優化階段即可讀取到資料。

ref:使用普通的索引(Normal Index)。

range:對索引進行範圍檢索。

當 type=index 時,索引物理檔案全掃,速度非常慢。

18.單表索引建議控制在5個以內。

19.單索引欄位數不允許超過5個。

欄位超過5個時,實際已經起不到有效過濾資料的作用了。

20.建立索引時避免以下錯誤觀念

索引越多越好,認為一個查詢就需要建一個索引。

寧缺勿濫,認為索引會消耗空間、嚴重拖慢更新和新增速度。

抵制惟一索引,認為業務的惟一性一律需要在應用層通過“先查後插”方式解決。

過早優化,在不瞭解系統的情況下就開始優化。

問題詳解

這部分,我將列出平時會遇到的一些問題,並給予解答。

1. 請問如下三條 SQL 該如何建立索引?

where a=1and b=1

where b=1

where b=1order by time desc

MySQL 的查詢優化器會自動調整 where 子句的條件順序以使用適合的索引嗎?

回答:             


第一問:建議建立兩個索引,即 idxab(a,b) 和 idxbtime(b,time)。

第二問:MySQL 的查詢優化器會自動調整 where 子句的條件順序以使用適合的索引,對於上面的第一條 SQL,如果建立索引為 idxba(b,a) 也是可以用到索引的,不過建議 where 後的欄位順序和聯合索引保持一致,養成好習慣。

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

select * fromemployees.titles where emp_no between '10001' and'10010' and title='Senior Engineer' and from_datebetween '1986-01-01'and '1986-12-31'

回答:可以使用索引,可以用到索引全部三個列,這個 SQL 看起來是用了兩個範圍查詢,但作用於 empno 上的“between”實際上相當於“in”,也就是說 empno 實際是多值精確匹配,在 MySQL 中要謹慎地區分多值匹配和範圍匹配,否則會對 MySQL 的行為產生困惑。

3.既然索引可以加快查詢速度,那麼是不是隻要是查詢語句需要,就建上索引?

回答:不是,因為索引雖然加快了查詢速度,但索引也是有代價的。索引檔案本身要消耗儲存空間,同時索引會加重插入、刪除和修改記錄時的負擔。另外,MySQL 在執行時也要消耗資源維護索引,因此索引並不是越多越好。一般兩種情況下不建議建索引。第一種情況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,沒必要建索引,另一種是資料的區分度比較低,可以使用 count(distinct(列名))/count(*) 來計算區分度。

4.主鍵和聚集索引的關係?

回答:在 MySQL 中,InnoDB 引擎表是(聚集)索引組織表(Clustered IndexOrganize Table),它會先按照主鍵進行聚集,如果沒有定義主鍵,InnoDB 會試著使用唯一的非空索引來代替,如果沒有這種索引,InnoDB 就會定義隱藏的主鍵然後在上面進行聚集。由此可見,在 InnoDB 表中,主鍵必然是聚集索引,而聚集索引則未必是主鍵。MyISAM 引擎表是堆組織表(Heap Organize Table),它沒有聚集索引的概念。

5.一個6億的表 a,一個3億的表 b,通過外來鍵 tid 關聯,如何最快的查詢出滿足條件的第50000到第50200中的這200條資料記錄?

回答:方法一:如果 a 表 tid 是自增長,並且是連續的,b表的id為索引。SQL語句如下。

select * froma,b where a.tid = b.id and a.tid>500000 limit200;

方法二:如果 a 表的 tid 不是連續的,那麼就需要使用覆蓋索引,tid 要麼是主鍵,要麼是輔助索引,b 表 id 也需要有索引。SQL語句如下。

select * fromb, (select tid from a limit 50000,200) awhere b.id = a.tid;

6.假如建立聯合索引(a,b,c),下列語句是否可以使用索引,如果可以,使用了那幾列?(考察聯合索引最左字首原則)

where a= 3

答:是,使用了 a 列。

where a= 3 and b = 5

答:是,使用了 a,b 列。

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 andc = 7

答:是,使用了 a,b 列。

where a = 3 and b like 'xx%' andc = 7

答:是,使用了 a,b 列。

7.文章表的表結構如下:

CREATE TABLEIF NOT EXISTS `article` (`id`int(10) unsigned NOT NULLAUTO_INCREMENT,

`author_id`int(10) unsignedNOT NULL,

`category_id`int(10) unsigned NOT NULL,

`views`int(10) unsignedNOT NULL,

`comments`int(10) unsignedNOT NULL,

`title`varbinary(255) NOT NULL,

`content`text NOTNULL,

PRIMARY KEY (`id`)

);

下面語句應該如何建立索引?

selectauthor_id, title, content from `article`

wherecategory_id = 1 and comments > 1

order byviews desc limit 1;

回答:

沒有聯合索引時,explain顯示,如下圖所示:

建立 idxcategoryidcommentsviews(category_id,comments, views) 聯合索引時,explain顯示,如下圖所示:

建立 idxcategoryidviews(categoryid,views) 聯合索引,explain 顯示,如下圖所示:

由此可見,可以建立 idxcategoryidviews(categoryid,views) 聯合索引。

結語

如果你想學好JAVA這門技術,也想在IT行業拿高薪,可以參加我們的訓練營課程,選擇最適合自己的課程學習,技術大牛親授,8個月後,進入名企拿高薪。我們的課程內容有:Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿里面試但擔心面試不過的,你都可以來,q群號為:180705916 進群免費領取學習資料。

相關文章