1. 程式人生 > >MySQL——索引優化實戰

MySQL——索引優化實戰

microsoft hdel 建立 性別 應用 tex 新增 獲取值 blog

上篇文章中介紹了索引的基本內容,這篇文章我們繼續介紹索引優化實戰。在介紹索引優化實戰之前,首先要介紹兩個與索引相關的重要概念,這兩個概念對於索引優化至關重要。

本篇文章用於測試的user表結構:

技術分享圖片

索引相關的重要概念

基數

單個列唯一鍵(distict_keys)的數量叫做基數。

SELECT COUNT(DISTINCT name),COUNT(DISTINCT gender) FROM user;

技術分享圖片

user表的總行數是5,gender 列的基數是 2,說明 gender 列裏面有大量重復值,name 列的基數等於總行數,說明 name列沒有重復值,相當於主鍵。

返回數據的比例:

user表中共有5條數據:

SELECT * FROM user;

技術分享圖片

查詢滿足性別為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能力是一個重要的指標。作為後端程序員,深以為然。

參考

  • 《深入淺出MySQL》



作者:擼碼那些事
微信公眾號:
技術分享圖片
來源:http://songwenjie.cnblogs.com/
聲明:本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。如果您認為還不錯,不妨點擊一下下方的推薦按鈕,謝謝支持。轉載與引用請註明出處。


MySQL——索引優化實戰