
上篇文章中介紹了索引的基本內容,這篇文章咱們繼續介紹索引優化實戰。在介紹索引優化實戰以前,首先要介紹兩個與索引相關的重要概念,這兩個概念對於索引優化相當重要。程序員
本篇文章用於測試的user表結構:數據庫
單個列惟一鍵(distict_keys)的數量叫作基數。後端
SELECT COUNT(DISTINCT name),COUNT(DISTINCT gender) FROM user;
緩存
user表的總行數是5,gender 列的基數是 2,說明 gender 列裏面有大量重複值,name 列的基數等於總行數,說明 name列沒有重複值,至關於主鍵。微信
返回數據的比例:併發
user表中共有5條數據:數據庫設計
SELECT * FROM user;
post
查詢知足性別爲0(男)的記錄數:性能
那麼返回記錄的比例數是:學習
同理,查詢name爲'swj'的記錄數:
返回記錄的比例數是:
如今問題來了,假設name、gender列都有索引,那麼SELECT * FROM user WHERE gender = 0;
SELECT * FROM user WHERE name = 'swj';
都能命中索引嗎?
user表的索引詳情:
SELECT * FROM user WHERE gender = 0;
沒有命中索引,注意filtered的值就是上面咱們計算的返回記錄的比例數。
SELECT * FROM user WHERE name = 'swj';
命中了索引index_name,由於走索引直接就能找到要查詢的記錄,因此filtered的值爲100
結論:
返回表中 30% 內的數據會走索引,返回超過 30% 數據就使用全表掃描。固然這個結論太絕對了,也並非絕對的30%,只是一個大概的範圍。
當對一個列建立索引以後,索引會包含該列的鍵值及鍵值對應行所在的 rowid。經過索引中記錄的 rowid 訪問表中的數據就叫回表。回表次數太多會嚴重影響 SQL 性能,若是回表次數太多,就不該該走索引掃描,應該直接走全表掃描。
EXPLAIN命令結果中的Using Index
意味着不會回表,經過索引就能夠得到主要的數據。Using Where
則意味着須要回表取數據。
有些時候雖然數據庫有索引,可是並不被優化器選擇使用。
咱們能夠經過SHOW STATUS LIKE 'Handler_read%';
查看索引的使用狀況:
Handler_read_key:若是索引正在工做,Handler_read_key的值將很高。
Handler_read_rnd_next:數據文件中讀取下一行的請求數,若是正在進行大量的表掃描,值將較高,則說明索引利用不理想。
索引優化規則:
若是MySQL估計使用索引比全表掃描還慢,則不會使用索引
返回數據的比例是重要的指標,比例越低越容易命中索引。記住這個範圍值——30%,後面所講的內容都是創建在返回數據的比例在30%之內的基礎上。
前導模糊查詢不能命中索引
name列建立普通索引:
前導模糊查詢不能命中索引:
EXPLAIN SELECT * FROM user WHERE name LIKE '%s%';
非前導模糊查詢則可使用索引,可優化爲使用非前導模糊查詢:
EXPLAIN SELECT * FROM user WHERE name LIKE 's%';
數據類型出現隱式轉換的時候不會命中索引,特別是當列類型是字符串,必定要將字符常量值用引號引發來
EXPLAIN SELECT * FROM user WHERE name=1;
EXPLAIN SELECT * FROM user WHERE name='1';
複合索引的狀況下,查詢條件不包含索引列最左邊部分(不知足最左原則),不會命中符合索引
name,age,status列建立複合索引:
ALTER TABLE user ADD INDEX index_name (name,age,status);
user表索引詳情:
SHOW INDEX FROM user;
根據最左原則,能夠命中複合索引index_name:
EXPLAIN SELECT * FROM user WHERE name='swj' AND status=1;
注意,最左原則並非說是查詢條件的順序:
EXPLAIN SELECT * FROM user WHERE status=1 AND name='swj';
而是查詢條件中是否包含索引最左列字段:
EXPLAIN SELECT * FROM user WHERE status=2 ;
union、in、or 都可以命中索引,建議使用 in。
union:
EXPLAIN SELECT * FROM user WHERE status = 1
UNION ALL
SELECT * FROM user WHERE status = 2;
in:
EXPLAIN SELECT * FROM user WHERE status IN (1,2);
or:
EXPLAIN SELECT * FROM user WHERE status=1 OR status=2;
查詢的CPU消耗:or > in >union
用or分割開的條件,若是or前的條件中列有索引,然後面的列中沒有索引,那麼涉及到的索引都不會被用到
EXPLAIN SELECT * FROM payment WHERE customer_id = 203 OR amount = 3.96;
由於or後面的條件列中沒有索引,那麼後面的查詢確定要走全表掃描,在存在全表掃描的狀況下,就沒有必要多一次索引掃描增長IO訪問。
負向條件查詢不能使用索引,能夠優化爲 in 查詢。
負向條件有:!=、<>、not in、not exists、not like 等。
status列建立索引:
ALTER TABLE user ADD INDEX index_status (status);
user表索引詳情:
SHOW INDEX FROM user;
負向條件不能命中緩存:
EXPLAIN SELECT * FROM user WHERE status !=1 AND status != 2;
能夠優化爲 in 查詢,可是前提是區分度要高,返回數據的比例在30%之內:
EXPLAIN SELECT * FROM user WHERE status IN (0,3,4);
範圍條件查詢能夠命中索引
範圍條件有:<、<=、>、>=、between等
status,age列分別建立索引:
ALTER TABLE user ADD INDEX index_status (status);
ALTER TABLE user ADD INDEX index_age (age);
user表索引詳情:
SHOW INDEX FROM user;
範圍條件查詢能夠命中索引:
EXPLAIN SELECT * FROM user WHERE status>5;
範圍列能夠用到索引(聯合索引必須是最左前綴),可是範圍列後面的列沒法用到索引,索引最多用於一個範圍列,若是查詢條件中有兩個範圍列則沒法全用到索引:
EXPLAIN SELECT * FROM user WHERE status>5 AND age<24;
若是是範圍查詢和等值查詢同時存在,優先匹配等值查詢列的索引:
EXPLAIN SELECT * FROM user WHERE status>5 AND age=24;
數據庫執行計算不會命中索引
EXPLAIN SELECT * FROM user WHERE age > 24;
EXPLAIN SELECT * FROM user WHERE age+1 > 24;
計算邏輯應該儘可能放到業務層處理,節省數據庫的 CPU的同時最大限度的命中索引。
利用覆蓋索引進行查詢,避免回表
被查詢的列,數據能從索引中取得,而不用經過行定位符 row-locator 再到 row 上獲取,即「被查詢列要被所建的索引覆蓋」,這可以加速查詢速度。
user表的索引詳情:
由於status字段是索引列,因此直接從索引中就能夠獲取值,沒必要回表查詢:
Using Index
表明從索引中查詢
EXPLAIN SELECT status FROM user where status=1;
當查詢其餘列時,就須要回表查詢,這也是爲何要避免SELECT *
的緣由之一:
EXPLAIN SELECT * FROM user where status=1;
創建索引的列,不容許爲 null
單列索引不存 null 值,複合索引不存全爲 null 的值,若是列容許爲 null,可能會獲得「不符合預期」的結果集,因此,請使用 not null 約束以及默認值。
remark列創建索引:
ALTER TABLE user ADD INDEX index_remark (remark);
IS NULL能夠命中索引:
EXPLAIN SELECT * FROM user WHERE remark IS NULL;
IS NOT NULL不能命中索引:
EXPLAIN SELECT * FROM user WHERE remark IS NOT NULL;
雖然IS NULL能夠命中索引,可是NULL自己就不是一種好的數據庫設計,應該使用NOT NULL 約束以及默認值
更新十分頻繁的字段上不宜創建索引
由於更新操做會變動B+樹,重建索引。這個過程是十分消耗數據庫性能的。
區分度不大的字段上不宜創建索引
相似於性別這種區分度不大的字段,創建索引的意義不大。由於不能有效過濾數據,性能和全表掃描至關。另外返回數據的比例在30%之外的狀況下,優化器不會選擇使用索引。
業務上具備惟一特性的字段,即便是多個字段的組合,也必須建成惟一索引
雖然惟一索引會影響insert速度,可是對於查詢的速度提高是很是明顯的。另外,即便在應用層作了很是完善的校驗控制,只要沒有惟一索引,在併發的狀況下,依然有髒數據產生。
多表關聯時,要保證關聯字段上必定有索引
建立索引時避免如下錯誤觀念
索引越多越好,認爲一個查詢就須要建一個索引。
寧缺勿濫,認爲索引會消耗空間、嚴重拖慢更新和新增速度。
抵制惟一索引,認爲業務的惟一性一概須要在應用層經過「先查後插」方式解決。
過早優化,在不瞭解系統的狀況下就開始優化。
對於本身編寫的SQL查詢語句,要儘可能使用EXPLAIN命令分析一下,作一個對SQL性能有追求的程序員。衡量一個程序員是否靠譜,SQL能力是一個重要的指標。做爲後端程序員,深覺得然。