索引(Index)
MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取數據的數據結構。索引的建立對於MySQL的高效運行是很重要的,索引可以大大提高MySQL的檢索速度。
拿漢語字典的目錄頁(索引)打比方,我們可以按拼音、筆畫、偏旁部首等排序的目錄(索引)快速查找到需要的字。
索引分單列索引和組合索引。單列索引,即一個索引只包含單個列,一個表可以有多個單列索引,但這不是組合索引。組合索引,即一個索引包含多個列。
創建索引時,你需要確保該索引是應用在 SQL 查詢語句的條件(一般作為 WHERE 子句的條件)。
實際上,索引也是一張表,該表保存了主鍵與索引字段,並指向實體表的記錄。
索引的原理
索引用於快速查找具有特定列值的行。如果沒有索引,MySQL必須從第一行開始,然后讀取整個表以查找相關行。表越大,成本越高。如果表中有相關列的索引,MySQL可以快速確定要在數據文件中間尋找的位置,而無需查看所有數據。這比按順序讀取每一行要快得多。
MySQL常用的是B+ Tree索引,下面詳細介紹。
b+樹

如上圖,是一顆b+樹,淺藍色的塊我們稱之為一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。
上圖中,如果要查找數據項29,那么首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因為非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那么總共需要百萬次的IO,顯然成本非常非常高。
通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據為N,每個磁盤塊的數據項的數量是m,則有

當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項占的空間越小,數據項的數量越多,樹的高度越低。這就是為什么每個數據項,即索引字段要盡量的小,比如int占4字節,要比bigint8字節少一半。
當b+樹的數據項是復合的數據結構,常見的就是組合索引,比如我們給某個表添加個組合索引,包括姓名、年齡和性別三列(name,age,sex),b+數是按照從左到右的順序來建立搜索樹的,比如查詢(where name=‘馬雲’ and age=18 and sex=1),b+樹會優先比較name來確定下一步的檢索方向,如果name相同再依次比較age和sex,最后得到檢索的數據;但如果我們查詢(where age=18 and sex= 1),此時索引是不生效的,因為建立搜索樹的時候name就是第一個比較因子,必須要先根據name來搜索才能知道下一步去哪里查詢。比如當查詢(where name='張三' and sex=2)的時候,b+樹可以用name來指定搜索方向,但下一個字段age的缺失,所以只能把名字等於張三的數據都找到,然后再匹配性別是2的數據了, 這個是非常重要的性質,即索引的最左匹配特性。
MySQL如何使用索引
MySQL使用索引進行這些操作:
-
WHERE快速 查找與子句匹配的行。 -
如果在多個索引之間有選擇,MySQL通常使用找到最小行數的索引。
-
如果表具有多列索引,即組合索引,則優化程序可以使用索引的任何最左前綴來查找行。例如,如果你有一個三列索引上(col1, col2, col3)
,你有索引的搜索功能(col1),(col1, col2)以及(col1, col2, col3)。 -
在執行連接時從其他表中檢索行。如果聲明它們的類型和大小相同,MySQL可以更有效地使用列上的索引。在這種情況下,
VARCHAR與CHAR被認為是相同的,如果它們被聲明為相同的大小。例如,VARCHAR(10)和CHAR(10)大小相同,但VARCHAR(10)與CHAR(15)不是。對於非二進制字符串列之間的比較,兩列應使用相同的字符集。例如,將
utf8列與latin1列進行比較會排除使用索引。不相似列的比較(例如,將字符串列與時間或數字列進行比較)可能會在沒有轉換的情況下無法直接比較值時阻止使用索引。對於給定的值,如
1在數值列,它可能比較等於在字符串列,例如任何數量的值'1',' 1','00001',或'01.e1'。這排除了對字符串列的任何索引的使用。 -
查找特定索引列的值Min()或 Max()[`值key_col。這是由預處理器優化的,該預處理器檢查您是否正在使用 索引之前出現的所有關鍵部分。在這種情況下,MySQL對每個或 表達式執行單個鍵查找,並用常量替換它。
-
對指定索引列進行排序或者分組,ORDER BY或者 GROUP BY
-
在某些情況下,可以優化查詢在不查詢整行數據的情況下檢索值。(為查詢提供所有必要結果的索引稱為 [覆蓋索引])如果查詢僅使用表中包含某些索引的列,則可以從索引樹中檢索所選值以獲得更快的速度:比如
SELECT key_part3 FROM tbl_name WHERE key_part1 = 1
對於小型表或報表查詢處理大多數或所有行的大型表的查詢,索引不太重要。當查詢需要訪問大多數行時,順序讀取比通過索引更快。順序讀取可以最大限度地減少磁盤搜索,即使查詢不需要所有行也是如此。
如何優化
-
主鍵優化
表的主鍵表示您在最重要的查詢中使用的列或列集。它具有關聯的索引,以實現快速查詢性能。查詢性能受益於
NOT NULL優化,因為它不能包含任何NULL值。使用InnoDB存儲引擎,表數據在物理上進行組織,以根據主鍵或列進行超快速查找和排序。如果您的表很大且很重要,但沒有明顯的列或列集用作主鍵,則可以創建一個單獨的列,其中包含自動增量值以用作主鍵。使用外鍵連接表時,這些唯一ID可用作指向其他表中相應行的指針。
-
外鍵優化
如果一個表有很多列,並且您查詢了許多不同的列組合,那么將頻率較低的數據拆分為每個都有幾列的單獨表可能會很有效,並通過外鍵將它們與主表關聯起來。這樣每個小表都可以有一個主鍵來快速查找其數據,您可以使用連接操作查詢所需的列集。根據數據的分布方式,查詢可能會執行較少的I / O並占用較少的高速緩存。(為了最大限度地提高性能,查詢嘗試從磁盤中讀取盡可能少的數據塊)。
-
列索引
最常見的索引類型涉及單個列,在數據結構中存儲該列的值的副本,允許快速查找具有相應列值的行。B樹數據結構可以讓索引快速查找特定值,一組值,或值的范圍,例如where條件中
=,>,≤,BETWEEN,IN等。每個存儲引擎定義每個表的最大索引數和最大索引長度。所有存儲引擎支持每個表至少16個索引,總索引長度至少為256個字節。
-
索引前綴
使用 字符串列的索引規范中的語法,可以創建僅使用列的前幾個字符的索引 。以這種方式僅索引列值的前綴可以使索引文件更小。索引 或 列時, 必須為索引指定前綴長度。
如果搜索項超過索引前綴長度,則索引用於排除不匹配的行,並檢查剩余的行以查找可能的匹配項。
-
FULLTEXT索引
FULLTEXT索引用於全文搜索。只有InnoDB和MyISAM存儲引擎支持FULLTEXT索引和僅適用於CHAR,VARCHAR和TEXT類型的列。索引始終發生在整個列上,並且不支持列前綴索引。 -
空間索引(Spatial Index)
您可以在空間數據類型上創建索引。
MyISAM和InnoDB支持空間類型的R樹索引。其他存儲引擎使用B樹來索引空間類型(除了ARCHIVE)。
-
-
多列索引
MySQL可以創建復合索引(即多列索引)。索引最多可包含16列。對於某些數據類型,您可以索引列的前綴。
MySQL可以對測試索引中所有列的查詢使用多列索引,或者只測試第一列,前兩列,前三列等的查詢。如果在索引定義中以正確的順序指定列,則單個復合索引可以加速同一表上的多種查詢。
假設一個表具有以下規范:
CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name) );在last_name
和first_name列創建了一個組合索引,它既可以查詢last_name和first_name`組合的值,也可以僅查詢last_name,因為該列是索引的最左前綴。因此,下面這些查詢是可以用到該索引的://只查詢last_name SELECT * FROM test WHERE last_name='Jones'; //同時查 SELECT * FROM test WHERE last_name='Jones' AND first_name='John'; SELECT * FROM test WHERE last_name='Jones' AND (first_name='John' OR first_name='Jon'); SELECT * FROM test WHERE last_name='Jones' AND first_name >='M' AND first_name < 'N';但是,該索引 不能用於以下查詢中的查找:
SELECT * FROM test WHERE first_name='John'; SELECT * FROM test WHERE last_name='Jones' OR first_name='John';假設您寫了如何SQL語句:
SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;如果col1和col2存在組合索引,那么可以直接獲取相應的行。如果col1和col2每列都存在單列索引,那么MySQL會優化合並索引,或者嘗試通過確定哪個索引會排除更多的行來查找限制性最強的索引。
如果表具有多列索引,則優化程序可以使用索引的最左前綴來查找行。例如,如果你有一個三列索引上
(col1, col2, col3),你有索引的搜索功能(col1),(col1, col2)以及(col1, col2, col3)。如果SQL語句不適用索引的最左前綴,則MySQL無法使用索引執行查找。例如以下查詢語句:
//使用索引 SELECT * FROM tbl_name WHERE col1=val1; //使用索引 SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2; //不使用索引 SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;如果存在索引
(col1, col2, col3),則只有前兩個查詢使用索引。第三和第四個查詢確實包括索引的列,但不使用索引來進行查找,因為(col2)和(col2, col3)不是的最左邊的前綴(col1, col2, col3)。
索引雖好,不可濫用
盡管為查詢中使用的每個可能列創建索引很有誘惑力,但不必要的索引會浪費空間並浪費時間讓MySQL確定要使用哪些索引。索引還會增加插入,更新和刪除的成本,因為必須更新每個索引。您必須找到適當的平衡,以使用最佳索引集實現快速查詢。
如何驗證索引使用情況?
我們創建了索引,但是我們如何確定mysql使用了索引? 答案是 使用explain語句。
下面會詳細介紹Explain,以及如何優化SQL。
SQL優化
explain查詢執行計划
舉個例子,最基礎的主鍵查詢
EXPLAIN SELECT
*
FROM
`subject`
WHERE
id = 1
執行結果如下:

再舉個關聯查詢的例子
EXPLAIN SELECT
a.*
FROM
`subject` a
LEFT JOIN subject_role_0 b ON a.id = b.subject_id
WHERE
a.id < 3
執行結果如下:

| 屬性 | 說明 |
|---|---|
| id | 查詢的序列號 |
| select_type | 查詢的類型 |
| table | 輸出結果集的表 |
| rows | 掃描的行數 |
| type | 連接類型,all表示采用全表掃描的方式。 |
| possible_keys | 可能使用的索引 |
| key | 實際使用的索引 |
| key_len | 索引字段的長度 |
| ref | 列與索引的比較 |
| Extra | 額外信息,比如使用了where語句,使用了join buffer等 |
id
id是sql執行順序的標識,按id從大到小的順序執行,在id相同時,執行順序是由上至下
select_type
select 查詢的類型,主要是用於區別普通查詢,聯合查詢,嵌套的復雜查詢
| 類型 | 說明 |
|---|---|
| simple | 簡單的select 查詢,查詢中不包含子查詢或者union |
| primary | 查詢中若包含任何復雜的子查詢,最外層查詢則被標記為primary |
| subquery | 在select或where 列表中包含了子查詢 |
| derived | 在from列表中包含的子查詢被標記為derived(衍生)MySQL會遞歸執行這些子查詢,把結果放在臨時表里。 |
| union | 若第二個select出現在union之后,則被標記為union,若union包含在from子句的子查詢中,外層select將被標記為:derived |
| union result | 從union表獲取結果的select |
table
查詢的數據庫的表的名稱,如果沒有給表指定別名,那么table值為表的名稱;否則table值為你指定的別名
type
表示MySQL在表中找到所需行的方式,這是一個非常重要的參數,常見的有:all , index , range , ref , eq_ref , const , system , null 八個級別。
常用的類型有: all、index、range、 ref、eq_ref、const、system、null(從左到右,性能從差到好)
| 類型 | 說明 |
|---|---|
| all | 全表掃描找到匹配的行,性能最差 |
| index | 全索引掃描,從索引樹找數據,比all性能好 |
| range | 只掃描指定范圍的行,使用索引來匹配行,常見使用between,in,>,<等關鍵字 |
| ref | 非唯一性索引掃描,本質上也是一種索引訪問,返回所有匹配某個單獨值的行。 |
| eq_ref | 唯一性索引掃描,對於每個索引鍵,表中有一條記錄與之匹配。 |
| const | 表示通過索引一次就可以找到,const用於比較primary key 或者unique索引。因為只匹配一行數據,所以很快 |
| system | 表只有一條記錄(等於系統表),這是const類型的特列,平時不會出現 |
| null | MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列里選取最小值可以通過單獨索引查找完成。 |
possible_keys
指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用(該查詢可以利用的索引,如果沒有任何索引顯示 null)
key
key列顯示MySQL實際決定使用的索引,必然包含在possible_keys中
key_len
顯示索引中使用的字節數,可通過key_len計算查詢中使用的索引長度。在不損失精確性的情況下索引長度越短越好。key_len 顯示的值為索引字段的最可能長度,並非實際使用長度,即key_len是根據表定義計算而得,並不是通過表內檢索出的。
ref
顯示索引的哪一列或常量被用於查找索引列上的值。
rows
很重要的一個參數,根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數,值越大說明掃描的行數越多,性能越差
Extra
該列包含MySQL解決查詢的詳細信息,有以下幾種情況:
| 值 | 說明 |
|---|---|
| Using where | 不用讀取表中所有信息,僅通過索引就可以獲取所需數據 |
| Using temporary | 表示需要使用臨時表來存儲結果集,常見於排序和分組查詢,如group by ,order by |
| Using filesort | 當查詢中包含 order by 操作,且無法利用索引完成的排序操作稱為“文件排序” |
| Using join buffer | 在獲取連接條件時沒有使用索引,並且需要連接緩沖區來存儲中間結果。如果出現了這個值,那應該注意,根據查詢的具體情況可能需要添加索引來改進能 |
| Impossible where | 強調了where語句會導致沒有符合條件的行 |
| Select tables optimized away | 這個值意味着僅通過使用索引,優化器可能僅從聚合函數結果中返回一行 |
| No tables used | Query語句中使用from dual 或不含任何from子句 |
優化數據庫結構
優化數據大小
以最小占用磁盤空間來設計表,這樣可以減少磁盤寫入和讀取來實現性能的提升。較小的表通常需要較少的主內存,同時索引也比較小,便於更快的處理。
通過使用此處列出的技術,您可以獲得更好的表性能並最大限度地減少存儲空間:
表列
- 盡可能使用最有效(最小)的數據類型。MySQL有許多專門的類型可以節省磁盤空間和內存。例如,如果可能,請使用較小的整數類型。mediumint通常是一個更好的選擇,它比int使用的列空間減少25%。
- 盡量使用NOT NULL列,它通過更好地使用索引並避免測試每個值是否為NULL來獲取更快的速度。
索引
- 表的主索引應盡可能短。這使得每行的識別變得簡單而有效。
- 僅創建提高查詢性能所需的索引。索引適用於檢索,但會降低插入和更新操作的速度。如果您主要通過搜索列的組合來訪問表,請在它們上創建單個復合索引,而不是為每列創建單獨的索引。索引的第一部分應該是最常用的列。如果從表中選擇時總是使用多列,則索引中的第一列應該是具有最多重復的列,以獲得更好的索引壓縮。
- 如果長字符串列很可能在第一個字符數上有唯一的前綴,那么最好只使用MySQL支持在列的最左邊部分創建索引來索引此前綴,較短的索引更快,不僅因為它們需要更少的磁盤空間,而且因為它們還會在索引緩存中為您提供更多的命中,從而減少磁盤搜索次數。
Join
- 在某些情況下,分成兩個經常掃描的表可能是有益的,如果它是動態格式表,則尤其如此,並且可以使用較小的靜態格式表,該表可用於在掃描表時查找相關行。
- 在具有相同數據類型的不同表中聲明具有相同信息的列,可以加速連接。
- 保持列名簡單,以便您可以在不同的表中使用相同的名稱並簡化連接查詢。例如表customer
,使用列名name而不是customer_name。
正常化
- 通常,盡量保持所有數據不冗余(第三范式),盡量通過引用join子句中的ID來連接查詢中的表。
- 如果速度比磁盤空間更重要,並且保留多個數據副本,那么可以創建匯總表到獲得更快的速度。
優化數據類型
對於唯一的id,首選使用數字列,而不是字符串,這是因為數字比字符串占用更少的字節,傳輸和比較速度更快,占用的內存更少。
優化字符和字符串類型
對於字符和字符串列,請遵循以下准則:
- 比較來自不同列的值時,請盡可能聲明具有相同字符集和排序規則的列,以避免在運行查詢時進行字符串轉換。
- 對於小於8KB的列值,請使用binary
VARCHAR而不是BLOB。 - 如果表包含字符串列(如名稱和地址),但許多查詢不檢索這些列,請考慮將字符串列拆分為單獨的表,並在必要時使用帶有外鍵的連接查詢。可以減少了常見查詢的磁盤I / O和內存使用。
優化BLOB類型
- 存儲包含文本數據的大blob時,請考慮先壓縮它。
- 對於具有多個列的表,要減少使用BLOB列的查詢,請考慮將BLOB列拆分為單獨的表,並在需要時使用連接查詢引用它。
