MySQL索引優化鐵則送你!!!|掘金技術徵文-雙節特別篇

前言

  • 索引的相信你們都據說過,可是真正會用的又有幾人?平時工做中寫SQL真的會考慮到這條SQL如何可以用上索引,如何可以提高執行效率?
  • 此篇文章詳細的講述了索引優化的幾個原則,只要在工做中可以隨時應用到,相信你寫出的SQL必定是效率最高,最牛逼的。
  • 文章的腦圖以下:

索引優化幾大規則

索引優化規則

一、like語句的前導模糊查詢不能使用索引

select * from doc where title like '%XX'--不能使用索引
select * from doc where title like 'XX%'--非前導模糊查詢,可使用索引
複製代碼
  • 由於頁面搜索嚴禁左模糊或者全模糊,若是須要可使用搜索引擎來解決。

二、union、in、or 都可以命中索引,建議使用 in

  1. union可以命中索引,而且MySQL 耗費的 CPU 最少。
select * from doc where status=1
union all
select * from doc where status=2;
複製代碼
  1. in可以命中索引,查詢優化耗費的 CPU 比 union all 多,但能夠忽略不計,通常狀況下建議使用 in
select * from doc where status in (1, 2);
複製代碼
  1. or 新版的 MySQL 可以命中索引,查詢優化耗費的 CPU 比 in多,不建議頻繁用or
select * from doc where status = 1 or status = 2
複製代碼
  1. 補充:有些地方說在where條件中使用or,索引會失效,形成全表掃描,這是個誤區:
  • ①要求where子句使用的全部字段,都必須創建索引;mysql

  • ②若是數據量太少,mysql制定執行計劃時發現全表掃描比索引查找更快,因此會不使用索引;sql

  • ③確保mysql版本5.0以上,且查詢優化器開啓了index_merge_union=on, 也就是變量optimizer_switch裏存在index_merge_union且爲on數據庫

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

  • 負向條件有:!=<>not innot existsnot like 等。性能優化

  • 例以下面SQL語句:markdown

select * from doc where status != 1 and status != 2;
複製代碼
  • 能夠優化爲 in 查詢:
select * from doc where status in (0,3,4);
複製代碼

四、聯合索引最左前綴原則

  • 若是在(a,b,c)三個字段上創建聯合索引,那麼他會自動創建 a| (a,b) | (a,b,c)組索引。數據庫設計

  • 登陸業務需求,SQL語句以下:函數

select uid, login_time from user where login_name=? andpasswd=?
複製代碼
  • 能夠創建(login_name, passwd)的聯合索引。由於業務上幾乎沒有passwd 的單條件查詢需求,而有不少login_name 的單條件查詢需求,因此能夠創建(login_name, passwd)的聯合索引,而不是(passwd, login_name)。
  1. 創建聯合索引的時候,區分度最高的字段在最左邊
  1. 存在非等號和等號混合判斷條件時,在創建索引時,把等號條件的列前置。如 where a>? and b=?,那麼即便a 的區分度更高,也必須把 b 放在索引的最前列。
  1. 最左前綴查詢時,並非指SQL語句的where順序要和聯合索引一致
  • 下面的 SQL 語句也能夠命中 (login_name, passwd) 這個聯合索引:
select uid, login_time from user where passwd=? andlogin_name=?
複製代碼
  • 但仍是建議 where 後的順序和聯合索引一致,養成好習慣。
  1. 假如index(a,b,c), where a=3 and b like 'abc%' and c=4a能用,b能用,c不能用。

五、不能使用索引中範圍條件右邊的列(範圍列能夠用到索引),範圍列以後列的索引全失效

  • 範圍條件有:<、<=、>、>=、between等。
  • 索引最多用於一個範圍列,若是查詢條件中有兩個範圍列則沒法全用到索引。
  • 假若有聯合索引 (empno、title、fromdate),那麼下面的 SQL 中 emp_no 能夠用到索引,而titlefrom_date 則使用不到索引。
select * from employees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31' 複製代碼

六、不要在索引列上面作任何操做(計算、函數),不然會致使索引失效而轉向全表掃描

  • 例以下面的 SQL 語句,即便 date 上創建了索引,也會全表掃描:
select * from doc where YEAR(create_time) <= '2016';
複製代碼
  • 可優化爲值計算,以下:
select * from doc where create_time <= '2016-01-01';
複製代碼
  • 好比下面的 SQL 語句:
select * from order where date < = CURDATE();
複製代碼
  • 能夠優化爲:
select * from order where date < = '2018-01-2412:00:00';
複製代碼

七、強制類型轉換會全表掃描

  • 字符串類型不加單引號會致使索引失效,由於mysql會本身作類型轉換,至關於在索引列上進行了操做。
  • 若是 phone 字段是 varchar 類型,則下面的 SQL 不能命中索引。
select * from user where phone=13800001234
複製代碼
  • 能夠優化爲:
select * from user where phone='13800001234';
複製代碼

八、更新十分頻繁、數據區分度不高的列不宜創建索引

  • 更新會變動 B+ 樹,更新頻繁的字段創建索引會大大下降數據庫性能。post

  • 「性別」這種區分度不大的屬性,創建索引是沒有什麼意義的,不能有效過濾數據,性能與全表掃描相似。性能

  • 通常區分度在80%以上的時候就能夠創建索引,區分度可使用 count(distinct(列名))/count(*) 來計算。優化

九、利用覆蓋索引來進行查詢操做,避免回表,減小select * 的使用

  • 覆蓋索引:查詢的列和所創建的索引的列個數相同,字段相同。
  • 被查詢的列,數據能從索引中取得,而不用經過行定位符 row-locator 再到 row 上獲取,即「被查詢列要被所建的索引覆蓋」,這可以加速查詢速度。
  • 例如登陸業務需求,SQL語句以下。
Select uid, login_time from user where login_name=? and passwd=?
複製代碼
  • 能夠創建(login_name, passwd, login_time)的聯合索引,因爲 login_time 已經創建在索引中了,被查詢的 uidlogin_time 就不用去 row 上獲取數據了,從而加速查詢。

十、索引不會包含有NULL值的列

  • 只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時,儘可能使用not null 約束以及默認值。

十一、is null, is not null沒法使用索引

十二、若是有order by、group by的場景,請注意利用索引的有序性

  1. order by 最後的字段是組合索引的一部分,而且放在索引組合順序的最後,避免出現file_sort 的狀況,影響查詢性能。
  • 例如對於語句 where a=? and b=? order by c,能夠創建聯合索引(a,b,c)
  1. 若是索引中有範圍查找,那麼索引有序性沒法利用,如 WHERE a>10 ORDER BY b;,索引(a,b)沒法排序。

1三、使用短索引(前綴索引)

  • 對列進行索引,若是可能應該指定一個前綴長度。例如,若是有一個CHAR(255)的列,若是該列在前10個或20個字符內,能夠作到既使得前綴索引的區分度接近全列索引,那麼就不要對整個列進行索引。由於短索引不只能夠提升查詢速度並且能夠節省磁盤空間和I/O操做,減小索引文件的維護開銷。可使用count(distinct leftIndex(列名, 索引長度))/count(*) 來計算前綴索引的區分度。

  • 但缺點是不能用於 ORDER BYGROUP BY 操做,也不能用於覆蓋索引。

  • 不過不少時候不必對全字段創建索引,根據實際文本區分度決定索引長度便可。

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

  • MySQL 並非跳過 offset 行,而是取 offset+N 行,而後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就很是的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。
  • 示例以下,先快速定位須要獲取的id段,而後再關聯:
selecta.* from1 a,(select id from1 where 條件 limit100000,20 ) b where a.id=b.id;
複製代碼

1五、若是明確知道只有一條結果返回,limit 1 可以提升效率

  • 好比以下 SQL 語句:
select * from user where login_name=?;
複製代碼
  • 能夠優化爲:
select * from user where login_name=? limit 1
複製代碼
  • 本身明確知道只有一條結果,但數據庫並不知道,明確告訴它,讓它主動中止遊標移動。

1六、超過三個表最好不要 join

  • 須要 join 的字段,數據類型必須一致,多表關聯查詢時,保證被關聯的字段須要有索引。

  • 例如:left join是由左邊決定的,左邊的數據必定都有,因此右邊是咱們的關鍵點,創建索引要建右邊的。固然若是索引在左邊,能夠用right join

1七、單表索引建議控制在5個之內

1八、SQL 性能優化 explain 中的 type:至少要達到 range 級別,要求是 ref 級別,若是能夠是 consts 最好

  • consts:單表中最多隻有一個匹配行(主鍵或者惟一索引),在優化階段便可讀取到數據。

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

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

  • type=index 時,索引物理文件全掃,速度很是慢。

1九、業務上具備惟一特性的字段,即便是多個字段的組合,也必須建成惟一索引

  • 不要覺得惟一索引影響了 insert 速度,這個速度損耗能夠忽略,但提升查找速度是明顯的。另外,即便在應用層作了很是完善的校驗控制,只要沒有惟一索引,根據墨菲定律,必然有髒數據產生。

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

  1. 索引越多越好,認爲須要一個查詢就建一個索引。

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

  3. 抵制唯一索引,認爲業務的唯一性一概須要在應用層經過「先查後插」方式解決。

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

索引選擇性與前綴索引

  • 既然索引能夠加快查詢速度,那麼是否是隻要是查詢語句須要,就建上索引?答案是否認的。由於索引雖然加快了查詢速度,但索引也是有代價的:索引文件自己要消耗存儲空間,同時索引會加劇插入、刪除和修改記錄時的負擔,另外,MySQL在運行時也要消耗資源維護索引,所以索引並非越多越好。通常兩種狀況下不建議建索引。

  • 第一種狀況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,不必建索引,讓查詢作全表掃描就行了。至於多少條記錄纔算多,這個我的有我的的見解,我我的的經驗是以2000做爲分界線,記錄數不超過 2000能夠考慮不建索引,超過2000條能夠酌情考慮索引。

  • 另外一種不建議建索引的狀況是索引的選擇性較低。所謂索引的選擇性(Selectivity),是指不重複的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:

Index Selectivity = Cardinality / #T
複製代碼
  • 顯然選擇性的取值範圍爲(0, 1]``,選擇性越高的索引價值越大,這是由B+Tree的性質決定的。例如,employees.titles表,若是title`字段常常被單獨查詢,是否須要建索引,咱們看一下它的選擇性:
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
+-------------+
| Selectivity |
+-------------+
|      0.0000 |
+-------------+
複製代碼
  • title的選擇性不足0.0001(精確值爲0.00001579),因此實在沒有什麼必要爲其單獨建索引。

  • 有一種與索引選擇性有關的索引優化策略叫作前綴索引,就是用列的前綴代替整個列做爲索引key,當前綴長度合適時,能夠作到既使得前綴索引的選擇性接近全列索引,同時由於索引key變短而減小了索引文件的大小和維護開銷。下面以employees.employees表爲例介紹前綴索引的選擇和使用。

  • 假設employees表只有一個索引<emp_no>,那麼若是咱們想按名字搜索一我的,就只能全表掃描了:

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 300024 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
複製代碼
  • 若是頻繁按名字搜索員工,這樣顯然效率很低,所以咱們能夠考慮建索引。有兩種選擇,建<first_name><first_name, last_name>,看下兩個索引的選擇性:
SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.0042 |
+-------------+
SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9313 |
+-------------+
複製代碼
  • <first_name>顯然選擇性過低,``<first_name, last_name>選擇性很好,可是first_namelast_name加起來長度爲30,有沒有兼顧長度和選擇性的辦法?能夠考慮用first_name和last_name的前幾個字符創建索引,例如<first_name, left(last_name, 3)>`,看看其選擇性:
SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.7879 |
+-------------+

複製代碼
  • 選擇性還不錯,但離0.9313仍是有點距離,那麼把last_name前綴加到4:
SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9007 |
+-------------+
複製代碼
  • 這時選擇性已經很理想了,而這個索引的長度只有18,比<first_name, last_name>短了接近一半,咱們把這個前綴索引建上:
ALTER TABLE employees.employees
ADD INDEX `first_name_last_name4` (first_name, last_name(4));
複製代碼
  • 此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:
SHOW PROFILES;
+----------+------------+---------------------------------------------------------------------------------+
| Query_ID | Duration   | Query                                                                           |
+----------+------------+---------------------------------------------------------------------------------+
|       87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
|       90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
+----------+------------+---------------------------------------------------------------------------------+
複製代碼
  • 性能的提高是顯著的,查詢速度提升了120多倍。

  • 前綴索引兼顧索引大小和查詢速度,可是其缺點是不能用於ORDER BYGROUP BY操做,也不能用於Covering index(即當索引自己包含查詢所需所有數據時,再也不訪問數據文件自己)。

總結

  • 本文主要講了索引優化的二十個原則,但願讀者喜歡。
  • 本篇文章腦圖PDF文檔已經準備好,有須要的夥伴能夠回覆關鍵詞索引優化獲取。

🏆 掘金技術徵文|雙節特別篇

相關文章
相關標籤/搜索