MySQL查詢優化

索引是什麼

官方定義:索引(Index)是幫助MySQL高效獲取數據的數據結構。

所以索引是一種數據結構,可以理解爲“排好序的快速查找數據結構”。一般來說,索引本身也很大,不可能全部存儲在內存,因此往往以索引文件的形式存儲在磁盤上。

我們平常所說的索引,如果沒有特別指明,都是B樹(多路搜索樹,不一定是二叉樹)索引。其中聚集索引、次要索引、複合索引、前綴索引、唯一索引默認都是使用B+樹

優勢

類似書本的目錄,提高查找效率,降低數據庫IO成本。

通過索引列對數據排序,降低數據排序成本,降低CPU消耗。

劣勢

  • 索引實際上也是一張表,保存了主鍵與索引字段,並指向實體表記錄,所以也是要佔用空間的。
  • 索引雖然大大提高了查詢速度,同時會降低更新表的速度。因爲更新表時,MySQL不僅要更新數據,還要更新索引。
  • 索引只是提高效率的一個因素,並不是建了索引或者用到了索引,查詢就很快。

MySQL索引分類

  • 單值索引:即一個索引只包含單個列,一個表可以有多個單列索引。
  • 唯一索引:索引列的值必須唯一,但允許空值。
  • 複合索引:即一個索引包含多個列。

基本語法

創建
//如果是CHAR,VARCHAR類型,length可以小於字段實際長度;如果是BLOB和TEXT類型,必須指定length
CREATE [UNIQUE] INDEX indexName ON tableName(columnName(length));
ALTER tableName ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
刪除
DROP INDEX [indexName] ON tableName;
查看
SHOW INDEX FROM tableName;

哪些情況需要/不需要建索引

  1. 主鍵自動建立唯一索引
  2. 頻繁作爲查詢條件的字段應該創建索引
  3. 查詢中與其它表關聯的字段,外鍵關係建立索引
  4. 頻繁更新的字段不適合創建索引
  5. Where條件後面用不到的字段不需要建立索引
  6. 需要排序的字段,索引是排好序的,所以通過索引排序將大大提高排序速度
  7. 統計或者分組的字段
  8. 表記錄太少不要建立索引
  9. 經常增刪的表不要建立索引
  10. 數據重複且分佈均勻的字段不需要建立索引

性能分析

explain

explain是什麼?

使用explain關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是怎麼處理你的SQL語句的,使用explian+SQL可以看到以下信息

+------+-------------+---------+------+---------------+------+---------+------+------+-------+
| id   | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra |
+------+-------------+---------+------+---------------+------+---------+------+------+-------+

id:包含一組數字,表示查詢或者操作表的順序。id相同,執行順序由上至下;id不同,如果是子查詢,id的序號會遞增,id越大,被執行優先級越高。

select_type:查詢的類型,主要用於區別普通查詢、聯合查詢、子查詢等的複雜查詢

  • SIMPLE:簡單select查詢,查詢中不包含子查詢或者UNION
  • PRIMARY:查詢中若包含任何複雜的子部分,則最外層類型查詢被標記爲該類型
  • SUBQUERY:子查詢,在select或者where列表中包含了子查詢
  • DERIVED:在from列表中包含的子查詢被標記爲DERIVED(衍生),MySQL會遞歸執行這些子查詢,把結果放在臨時表裏。
  • UNION:若第二個select出現在UNION之後,則被標記爲UNION;若UNION包含在from子句的子查詢中,外層select將被標記爲DERIVED
  • UNION RESULT:從UNION表獲取結果的select

table:顯示是哪張表

type:顯示查詢使用了何種類型

+------+-------+-------+-----+--------+--------+---------+------+
| ALL  | index | range | ref | eq_ref | const  | system  | NULL |
+------+-------+-------+-----+--------+--------+---------+------+

從好到壞依次爲:system>const>eq_ref>ref>range>index>ALL

  • system:表裏只有一行記錄(等於系統表),這是const類型的特例
  • const:const用於比較primary key或者union索引,因爲只匹配一行數據,一次就能找到數據,所以很快
  • eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或者唯一性索引掃描
  • ref:非唯一索引掃描,返回匹配某個單獨值的所有行,可能返回多個,所以屬於查找個掃描的混合體
  • range:只檢索給定範圍的行,使用一個索引來選擇行,一般就是where條件後面出現了between、<、>、in等條件。這種不需要全索引掃描,所以比全索引掃描效率高
  • index:Full Index Scan,全索引掃描,因爲索引文件通常比數據文件小,所以全索引掃描通常比全表掃描快
  • ALL:全表掃描

一般來說最少要達到range級別。

possible_keys:查詢涉及到的字段若存在索引,則該索引將被列出,但不一定被實際使用

key:實際使用的索引,若爲NULL則沒又使用索引。查詢中若使用了覆蓋索引,則該索引和查詢的select字段重疊

key_len:表示索引中使用的字節數,在不損失準確性的情況下,長度越短越好。key_len顯示的值爲索引字段的最大可能長度,並非實際長度。即是根據表定義計算而得,不是通過表內檢索得出。

ref:顯示索引的哪一列被使用了

rows:根據表統計信息及索引選用情況,大致估算出找到所需要的記錄需要讀取的行數

Extra:包含不適合在其它列顯示,但十分重要的信息

  • Using filesort:說明MySQL會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中無法利用索引完成排序的操作稱爲“文件排序”
  • Using temporary:使用了臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表,常見於order by和group by
  • Using index:表示select操作中使用了覆蓋索引,避免了訪問表的數據行。如果同時出現using where,表明索引被用來執行索引鍵值的查找;如果沒有同時出現using where,表面索引用來讀取數據而非查找。
  • Using wher:表明使用了where過濾
  • Using join buffer:使用了連接緩存
  • impossible where:表明where總是false,例如:where a=1 and a=2
  • select tables optimized away:在沒有group by子句的情況下,基於索引優化MIN/MAX操作或者對於MyISAM存儲引擎優化COUNT(*)操作,不必等到執行階段再進行計算,查詢執行計劃生成的階段即完成優化
  • distinct:優化distinct操作,在找到第一行匹配的元素後即停止找同樣值的動作

避免索引失效

  • 如果索引使用了多列,最好按照索引字段的順序從左至右使用,並且不跳過中間字段
  • 不在索引列上做任何操作(計算、函數、自動或者手動類型轉換),否則會導致索引失效
  • 存儲引擎不能使用索引中範圍條件右邊的列。(例如abc三個字段組成一個符合索引,條件爲where a=1 and b>1 and c=1,則無法使用索引中c的列)
  • 儘量使用覆蓋索引(只訪問索引的查詢),減少select *
  • MySQL在使用不等於(!=或者<>)、is null、is not null的時候無法使用索引
  • like以通配符開頭的索引失效,則會變成全表掃描。(使用覆蓋索引可以避免)
  • 少用or,用它連接時會使索引失效

order by 關鍵字

ORDER BY子句,儘量使用Index方式排序,避免使用FileSort方式排序,MySQL支持Index和FileSot兩種方式排序,Index效率高,它指MySQL掃描索引本身完成排序。FileSort方式效率較低。

ORDER BY滿足兩種情況會使用Index方式排序:1.ORDER BY子句使用索引最左前列;2.使用WHERE子句與ORDER BY子句條件列組合滿足索引最左前列。

如果不在索引列上,FileSort有兩種算法:雙路排序和單路排序

雙路排序:MySQL4.1之前使用的是雙路排序,就是兩次掃描磁盤,最終得到數據。先從磁盤取排序字段,在buffer進行排序,再從磁盤中取出其它字段。取一批數據要進行掃描,IO是很耗時的,所以在MySQL4.1之後出現了第二種改進的算法,單路排序

單路排序:從磁盤中讀取查詢的所有列,在buffer對它們進行排序,然後掃描排序後的列表進行輸出。避免兩次IO,但是使用了更多空間。

單路排序需要更多的空間,所以可以通過增大sort_buffer_size和max_length_for_sort_data這兩個參數來優化排序

group by 關鍵字

GROUP BY實質上是先排序後分組,所以策略基本跟ORDER BY一樣。當無法使用索引列來分組,可以通過增大sort_buffer_size和max_length_for_sort_data這兩個參數來優化分組。

慢查詢日誌

當通過explain不能解決SQL查詢慢的問題,可以通過開啓慢查詢日誌來繼續排查

//查看慢日誌是否開啓ON爲開啓,OFF爲關閉
show variables like '%slow_query_log%'
//開啓慢查詢日誌
set global slow_query_log='ON';
set global slow_query_log=1;
//關閉慢查詢日誌
set global slow_query_log='OFF';
set global slow_query_log=0;
//查詢當前的慢查詢多少秒算慢
show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
//設置查詢SQL超過5秒,記錄慢查詢日誌,設置後要重新連接或者開個會話才生效
set global long_query_time=5;
//查詢有多少條慢查詢
show global status like '%slow_queries%';

show profile

用來分析SQL語句執行的資源消耗情況,默認關閉狀態,保存最近15次的運行結果

show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | OFF   |
+---------------+-------+
//開啓
set global profiling='ON';
set global profiling=1;
//關閉
set global profiling='OFF';
set global profiling=0;
show profiles;
+----------+------------+----------------------------------+
| Query_ID | Duration   | Query                            |
+----------+------------+----------------------------------+
|        1 | 0.00019383 | select @@version_comment limit 1 |
|        2 | 0.00052840 | show variables like 'profiling'  |
+----------+------------+----------------------------------+

show profile cpu,block io for query 1;
+----------------------+----------+----------+------------+--------------+---------------+
| Status               | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting             | 0.000065 | 0.000030 |   0.000029 |            0 |             0 |
| checking permissions | 0.000009 | 0.000005 |   0.000005 |            0 |             0 |
| Opening tables       | 0.000006 | 0.000003 |   0.000003 |            0 |             0 |
| After opening tables | 0.000013 | 0.000006 |   0.000006 |            0 |             0 |
| init                 | 0.000021 | 0.000011 |   0.000011 |            0 |             0 |
| optimizing           | 0.000015 | 0.000008 |   0.000007 |            0 |             0 |
| executing            | 0.000016 | 0.000008 |   0.000008 |            0 |             0 |
| end                  | 0.000009 | 0.000004 |   0.000004 |            0 |             0 |
| query end            | 0.000006 | 0.000004 |   0.000003 |            0 |             0 |
| closing tables       | 0.000005 | 0.000002 |   0.000003 |            0 |             0 |
| freeing items        | 0.000008 | 0.000004 |   0.000004 |            0 |             0 |
| updating status      | 0.000012 | 0.000007 |   0.000006 |            0 |             0 |
| cleaning up          | 0.000007 | 0.000003 |   0.000003 |            0 |             0 |
+----------------------+----------+----------+------------+--------------+---------------+

參數:

  • ALL 顯示所有性能信息
  • BLOCK IO 顯示塊IO操作的次數
  • CONTEXT SWITCHES 顯示上下文切換次數,不管是主動還是被動
  • CPU 顯示用戶CPU時間、系統CPU時間
  • IPC 顯示發送和接收的消息數量
  • MEMORY [暫未實現]
  • PAGE FAULTS 顯示頁錯誤數量
  • SOURCE 顯示源碼中的函數名稱與位置
  • SWAPS 顯示SWAP的次數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章