MYSQL索引最佳實踐

你做了一個明智的選擇

  • 理解索引對開發和dba來說都是極其重要

  • 差勁的索引對產品問題負相當大的一部分責任

  • 索引不是多麼高深的問題

MySQL 索引一覽表

  • 理解索引

  • 爲你的應用創建最佳索引

  • 擁抱MySQL的限制

簡述索引

索引有什麼用

  • 爲從數據庫讀取數據加速

  • 強制約束 (唯一索引 UNIQUE, 外鍵 FOREIGN KEY)

  • 沒有任何索引的情況下查詢頁能正常運行

  • 但是那可能需要執行很長的時間

你可能聽說過的索引類型

  • BTREE索引 – mysql中主要的索引類型

  • RTREE索引 – 只有MyISAM支持, 用於GIS

  • HASH 索引 – MEMORY, NDB 支持

  • BITMAP 索引 – MySQL 不支持

  • FULLTEXT 索引 – MyISAM, Innodb(MySQL 5.6以上支持)

類BTREE索引家族

  • 有很多不同的實現

    • 在可加速的操作中共享相同的屬性

    • 內存相比硬盤使生活變得美好

  • B+樹通常用於硬盤存儲

    • 數據存儲於葉子節點

B+Tree 示例

MyISAM、Innodb索引對比

  • MyISAM

    • 數據指針指向數據文件中的物理位置

    • 所有索引都是一樣的(指向物理位置))

  • Innodb

    • 主鍵索引 (顯式或隱式) - 直接將數據存儲於索引的葉子節點,而不是指針

    • 二級索引 – 保存主鍵索引的值作爲數據指針

BTREE索引能用於什麼操作 ?

  • 查詢所有 KEY=5 的記錄 (點查詢)

  • 查詢所有 KEY>5 的記錄 (開合間)

  • 查詢所有 5<KEY<10 的記錄 (閉合間)

  • 不適用於:查詢KEY最後一個數字等於0的所有記錄

    • 因爲這不能定義爲範圍查詢操作

字符索引

  • 這(和數值)沒什麼區別… 真的

    • collation是爲字符串定義的排序規則

    • 如: 「AAAA」 < 「AAAB」

  • 前綴LIKE 查詢是一種特殊的範圍查詢

    • LIKE 「ABC%」 的意思是:

    • 「ABC[最小值]」<KEY<「ABC[最大值]」

    • LIKE 「%ABC」 無法使用索引查詢

聯合索引

  • 是這樣進行排序的, 比較首列,然後第二列,第三列以此類推,如:

    • KEY(col1,col2,col3)

    • (1,2,3) < (1,3,1)

  • 使用一個BTREE索引,而不是每個層級一個單獨的BTREE索引

索引的開銷

  • 索引是昂貴的,不要添加多餘的索引

    • 多數情況下,擴展索引比添加一個新的索引要好

  • 寫 - 更新索引常常是數據庫寫操作的主要開銷

  • 讀 - 需要再硬盤和內存開銷空間; 查詢優化中需要額外的開銷

索引成本的影響

  • 長主鍵索引(Innodb) – 使所有相應的二級索引 變得更長、更慢

  • 「隨機」主鍵索引(Innodb) – 插入導致大量的頁面分割

  • 越長的索引通常越慢

  • Index with insertion in random order – SHA1(‘password’)

  • 低區分度的索引是低劣的 – 在性別字段建的索引

  • 相關索引是不太昂貴的– insert_time與自增id是相關的

Innodb表的索引

  • 數據按主鍵聚集

    • 選擇最佳的字段作爲主鍵

    • 比如評論表 – (POST_ID,COMMENT_ID) 是作爲主鍵的不錯選擇,使得單個post的評論聚在一起

  • 或者 「打包」 單個 BIGINT(字段)

  • 主鍵隱式地附加到所有索引中

    • KEY (A) 實質上是 KEY (A,ID)

  • 覆蓋索引,有利於排序

MySQL是如何使用索引的

  • 查詢

  • 排序

  • 避免讀取數據(只讀取索引)

  • 其他專門的優化

使用索引進行查詢

  • SELECT * FROM EMPLOYEES WHERELAST_NAME=「Smith」

    • 這是典型的索引 KEY(LAST_NAME)

  • 可以使用複合索引

    • SELECT * FROM EMPLOYEES WHERELAST_NAME=「Smith」 AND DEPT=「Accounting」

    • 將會使用索引 KEY(DEPT,LAST_NAME)

複合索引比較複雜

  • Index (A,B,C) - 字段順序問題

  • 下列情形將會使用索引進行查詢(全條件)

    • A>5

    • A=5 AND B>6

    • A=5 AND B=6 AND C=7

    • A=5 AND B IN (2,3) AND C>5

  • 下列條件將不會使用索引

    • B>5 – 條件沒有B字段前的A

    • B=6 AND C=7 - 條件沒有B、C字段前的A

  • 以下情形使用索引的一部分

    • A>5 AND B=2 - 第一個字段A的範圍查詢,導致只用上了索引中A字段的部分

    • A=5 AND B>6 AND C=2 - B字段的範圍範圍查詢,導致只使用了索引中A和B兩個字段的部分

MySQL優化器的第一法則

  • 在複合索引中,MySQL在遇到返回查詢(<,>,BETWEEN)時,將停止中止剩餘部分(索引)的使用;但是使用IN(…)的"範圍查詢"則可以繼續往右使用索引(的更多部分)

所用索引進行排序

  • SELECT * FROM PLAYERS ORDER BY SCOREDESC LIMIT 10

    • 將使用索引 KEY(SCORE)

    • 不使用索引將進行非常昂貴的「filesort」操作(externalsort)

  • 常常使用組合索引進行查詢

    • SELECT * FROM PLAYERS WHERE COUNTRY=「US」ORDER BY SCORE DESC LIMIT 10

    • 最佳選擇是 KEY(COUNTRY,SCORE)

高效排序的聯合索引

  • 變得更加受限!

  • KEY(A,B)

  • 以下情形將會使用索引進行排序

    • ORDER BY A - 對索引首字段進行排序

    • A=5 ORDER BY B - 對第一個字段進行點查詢,對第二個字段進行排序

    • ORDER BY A DESC, B DESC - 對兩個字段進行相同的順序進行排序

    • A>5 ORDER BY A - 對首字段進行範圍查詢,並對首字段進行排序

  • 以下情形將不使用索引進行排序

    • ORDER BY B - 對第二個字段進行排序(未使用首字段)

    • A>5 ORDER BY B – 對首字段進行範圍查詢,對第二個字段進行排序

    • A IN(1,2) ORDER BY B - 對首字段進行IN查詢,對第二個字段進行排序

    • ORDER BY A ASC, B DESC - 對兩個字段進行不同順序的排序

MySQL使用索引排序的規則

  • 不能對兩個字段進行不同順序的排序

  • 對非ORDER BY部分的字段只能使用點查詢(=)– 在這種情形下,IN()也不行

避免讀取數據(只讀取索引)

  • 「覆蓋索引」– 這裏指 適用於特定查詢的索引,而不是一種索引的類型

  • 只讀取索引,而不去讀取數據

    • SELECT STATUS FROM ORDERS WHERECUSTOMER_ID=123

    • KEY(CUSTOMER_ID,STATUS)

  • 索引通常比數據本身要小

  • (索引)讀取起來更有次序– 讀取數據指針通常是隨機的

Min/Max的優化

  • 索引可以幫助優化 MIN()/MAX() 這類的統計函數– 但只包含以下這些:

  • SELECT MAX(ID) FROM TBL;

  • SELECT MAX(SALARY) FROM EMPLOYEEGROUP BY DEPT_ID

    • 將受益於 KEY(DEPT_ID,SALARY)

    • 「Using index for group-by」

聯表查詢中索引的使用

  • MySQL 使用 「嵌套循環(Nested Loops)」進行聯表查詢

    • SELECT * FROM POSTS,COMMENTS WHEREAUTHOR=「Peter」 AND COMMENTS.POST_ID=POSTS.ID

    • 掃描表POSTS查詢所有複合條件的 posts

    • 循環posts 在表COMMENTS 中查找 每個post的所有comments

  • 使每個關聯的表(關聯字段)都使用上索引顯得非常的重要

  • 索引只有在被查詢的字段上是必要的– POSTS.ID字段的索引再本次查詢中是用不上的

  • 重新設計不能很好的所有索引的聯合查詢吧

使用多索引

  • MySQL可以使用超過1個索引

    • 「索引合併」

  • SELECT * FROM TBL WHERE A=5 AND B=6– 可以分別使用索引 KEY(A)和 KEY(B)

    • 索引 KEY(A,B) 是更好的選擇

  • SELECT * FROM TBL WHERE A=5 OR B=6– 兩個索引同時分別被使用

    • 索引 KEY(A,B) 在這個查詢中無法使用

前綴索引

  • 你可以在字段最左前綴建立索引

    • ALTER TABLE TITLE ADD KEY(TITLE(20));

    • 需要對BLOB/TEXT類型的字段建立索引

    • 能顯著的減少空間使用

    • 不能用於覆蓋索引

    • 選擇前綴長度成爲一個問題

選擇前綴長度

  • 前綴應該有足夠的區分度

    • 比較distinct前綴、distinct整個字段的值

mysql> select count(distinct(title)) total,count(distinct(left(title,10))) p10,count(distinct(left(title,20))) p20 from title;

total p10 p20
998335 624949 960894

1 row in set (44.19 sec)

  • 檢查異常值

    • 確保不會有很多記錄使用相同的前綴

使用最多的Titlemysql> select count(*) cnt, title tl from title group by tl order by cnt desc limit 3;

cnt tl
136 The Wedding
129 Lost and Found
112 Horror Marathon

3 rows in set (27.49 sec)

使用最多的Title 前綴 mysql> select count(*) cnt, left(title,20) tl from title group by tl order by cnt desc limit 3;

cnt tl
184 Wetten, dass..? aus
136 The Wedding
129 Lost and Found

3 rows in set (33.23 sec)

MySQL如何選擇使用哪個索引的?

  • 每次查詢動態選擇– 查詢文本中常量很重要

  • 評估需要查詢的行數 對給定的索引,在表中進行"dive"

  • 如果(dive)不可行時,使用 「Cardinality」 進行統計– 這是進行 ANALYZE TABLE時 更新的

更多關於索引的選擇

  • 並不只是最小化掃描行數

  • 很多其他的heuristics(嘗試) and hacks– 對Innodb來說主鍵是很重要的

    • 覆蓋索引效益

    • Full table scan is faster, all being equal(這句不是太明白)

    • 我們也可以使用索引進行排序

  • 須知

    • 驗證MYSQL實際使用的執行計劃

    • 注意是可以根據常量和數據動態改變的

使用EXPLAIN

  • EXPLAIN 是一個很好的工具,可以看到MYSQL將如何進行查詢

    mysql> explain select max(season_nr) from title group by production_year;

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE title range NULL production_year 5 NULL 201 Using index for group-by

1 row in set (0.01 sec)

MySQL Explain 101

  • 「type」 從好到差排序如下:– system,const,eq_ref,ref,range,index,ALL

  • 注意 「rows」 – 更大的數值意味着更慢的查詢

  • 檢查 「key_len」 – 顯示索引的哪些部分真實使用到了

  • 留意"Extra"

    • Using Index - 好

    • Using Filesort, Using Temporary - 差

索引策略

  • 爲你的關鍵性能查詢集建立索引– 整體取審視他們,而不是一個個看

  • 最好所有的查詢條件和聯表條件都使用索引– 起碼區分度最高的部分是

  • 一般來說,可以的話,擴展索引,而不是創建新的索引

  • 修改時記得驗證對性能的影響

索引策略示例

  • 按能支持更多查詢的順序建立索引

    • SELECT * FROM TBL WHERE A=5 AND B=6

    • SELECT * FROM TBL WHERE A>5 AND B=6– 對兩個查詢來說 KEY(B,A) 是更好的選擇

  • 把所有都是點查詢的字段放到索引的首位

  • 不要添加非性能關鍵查詢的索引– 太多的索引會使MYSQL慢下來

Trick #1: 枚舉範圍

  • KEY (A,B)

  • SELECT * FROM TBL WHERE A BETWEEN 2AND 4 AND B=5

    • 將只使用索引的第一個字段部分

  • SELECT * FROM TBL WHERE A IN (2,3,4) ANDB=5

    • 索引的兩個字段部分都使用

Trick #2: 添加一個假的條件

  • KEY (GENDER,CITY)

  • SELECT * FROM PEOPLE WHERE CITY=「NEWYORK」

    • 完全用不上索引

  • SELECT * FROM PEOPLE WHERE GENDER IN(「M」,」F」) AND CITY=「NEW YORK」

    • 將用上索引

    • 這個Trick在低區別度的字段上可以很好的使用

    • Gender, Status, Boolean Types etc

Trick #3: 虛實Filesort

  • KEY(A,B)

  • SELECT * FROM TBL WHERE A IN (1,2) ORDER BYB LIMIT 5;

    • 無法使用索引進行排序

  • (SELECT FROM TBL WHERE A=1 ORDER BY B LIMIT 5) UNION ALL (SELECT FROM TBL WHERE A=2 ORDER BY B LIMIT 5) ORDER BY B LIMIT 5;

    • 將會用上索引,而「filesort」只用於對不超過10行記錄